2020년 2학기 파이썬 두번째 스터디 정리
오늘은 클래스 사용법, raise
사용법, exception 처리, tuple 자료형, set 자료형을 다루었고, iterator와 관련된 map
함수도 짧게 다루었습니다.
클래스
클래스를 선언하는 방법은 가장 간단한 방법은 pass
를 사용하는 것입니다.
class Car:
pass
이렇게 선언한 클래스는
car = Car()
처럼 초기화할 수 있습니다. 그리고 pass
를 사용한 선언은 아래 코드와 똑같다고 보시면 됩니다.
class Car:
def __init__(self):
pass
아래와 같은 방식으로 클래스에 메소드를 선언하고 사용할 수 있습니다.
class Car:
def wheels(self):
return 4
car = Car()
print(car.wheels()) # 4 출력
클래스를 사용하는 이유엔 여러가지가 있습니다. 예를 들어 약마다 작용이 다르므로 먹었을 때의 효과 역시 다를 것이고, 이를 코드로 표현할 때 쓰이는 도구가 클래스입니다.
또한, 클래스는 관련된 변수를 묶어주는 데에도 사용됩니다.
게임 플레이어가 4명이 있다고 생각합시다. 클래스를 사용하지 않으면 player1_hp
, player1_max_hp
,player1_mp
, player1_max_mp
,player2_hp
, player2_max_hp
,player2_mp
, player2_max_mp
, player3_hp
, ... 이런 식으로 변수가 매우 많이 필요해집니다.
변수가 많으면 실수할 가능성도 올라가고, 유지보수에도 좋지 않습니다. 따라서 이런 경우에도 클래스를 사용합니다.
상속
아래 코드를 보면 Car
클래스는 run
, stop
이라는 이름의 메소드를 정의하지도 없고, running
이라는 프로퍼티도 정의하지 않습니다.
class Vehicle:
def run(self):
print('Vehicle.run()')
self.running = True
def stop(self):
print('Vehicle.stop()')
self.running = False
class Car(Vehicle):
pass
car = Car()
car.run()
assert car.running
car.stop()
assert not car.running
그럼에도 위 코드가 잘 실행되는 건, Car
이라는 클래스가 Vehicle
이라는 클래스를 상속하기 때문입니다.
오버로딩
class Vehicle:
def run(self):
print('Vehicle.run()')
self.running = True
def stop(self):
print('Vehicle.stop()')
self.running = False
class Car(Vehicle):
def run(self):
print('Car.run()')
self.running = True
car = Car()
car.run()
assert car.running
car.stop()
assert not car.running
위 코드는
Car.run()
Vehicle.stop()
을 출력합니다. Vechicle
클래스의 run
이라는 메소드가 호출되지 않은 것이죠. 이를 오버라이딩이라고 부릅니다.
super 호출
메소드를 오버라이딩한 경우에도 아래처럼 부모 클래스의 함수를 호출할 수 있습니다.
class Vehicle:
def __init__(self):
self.running = False
def run(self):
print('Vehicle.run()')
self.running = True
def stop(self):
print('Vehicle.stop()')
self.running = False
class Car(Vehicle):
def run(self):
print('Starting Car.run()')
# super()을 써야합니다.
super().run()
print('Finishing Car.run()')
car = Car()
car.run()
car.stop()
위 코드는
Starting Car.run()
Vehicle.run()
Finishing Car.run()
Vehicle.stop()
를 출력합니다.
오류 발생 (raise)
0으로 나누기를 시도했을 때 한국어로 0으로 나눌 수 없습니다
라는 에러를 던지는 함수를 파이썬으로 구현하면 다음과 같습니다.
def divide(a, b):
if b == 0:
raise ArithmeticError('0으로 나눌 수 없습니다')
return a // b
예외 처리
프로그램이 실행되다보면 많은 예외 케이스가 있습니다. 와이파이가 끊긴다던가 input 파일이 없다던가...
try:
f = open('nonexistant-file.txt', 'r')
print('읽기 성공')
except FileNotFoundError as e:
print('읽기 실패')
print(e)
위 프로그램을 실행하면
읽기 실패
FileNoutFoundError (# elided)
이 출력됩니다. f = open('nonexistant-file.txt', 'r')
이 줄에서 exception이 발생하기에, 아래 줄은 실행되지 않은 것이죠.
여러가지 오류 처리
except
문을 여러 개 쓰면 여러가지 에러를 잡을 수 있습니다.
이때 주의하실 점이 하나 있는데요, 오류 클래스 간의 상속관계가 있다면 자식 클래스에 대한 except
문이 먼저 와야 한다는 점입니다.
try:
res = 5 // 0
# 순서 중요 (클래스 상속)
except ZeroDivisionError as e:
print('ZeroDivisionError 발생')
except ArithmeticError as e:
print('ArithmeticError 발생')
위 코드는 ZeroDivisionError 발생
을 출력합니다. 하지만
try:
res = 5 // 0
# 순서 중요 (클래스 상속)
except ArithmeticError as e:
print('ArithmeticError 발생')
except ZeroDivisionError as e:
print('ZeroDivisionError 발생')
위 코드는 ArithmeticError 발생
을 출력합니다. ZeroDivisionError
이 ArithmeticError
를 상속하기 때문인데,
except ZeroDivisionError as e:
print('ZeroDivisionError 발생')
이 부분이 실행되지 않는 dead code가 됩니다
전혀 다른 오류인 경우 순서가 중요하지 않습니다.
try:
# 주석 해제된 경우 아래의 연산이 실행되지 않음
# f = open('non-existing-file.txt', 'r')
res = 5 // 0
# 순서 중요 (클래스 상속)
except ZeroDivisionError as e:
print('ZeroDivisionError 발생')
print(e)
except ArithmeticError as e:
print('ArithmeticError 발생')
print(e)
except FileNotFoundError as e:
print('FileNotFoundError 발생')
print(e)
tuple 자료형
학교 수업 시간에 다루지 않았다고 해서 튜플 내용도 다루었습니다. 파이썬의 tuple 타입은 상당히 편리합니다.
arr = [1, 2, 3, 4, 5]
# 주의: 배열의 길이가 왼쪽의 변수 개수와 같아야 함
a, b, _, c, _ = arr
print(a)
print(b)
print(c)
위 코드는
1
2
4
를 출력합니다.
tuple 반환
파이썬에서는 함수에서 tuple 자료형을 반환할 수 있습니다.
def verify(s):
if 6 <= len(s) <= 12:
return True, ""
return False, "입력한 값이 올바르지 않습니다"
success, msg = verify("1000000")
# success, msg = verify("100000000258y7034957823")
print('success:', success)
print('msg:', msg)
위 코드를 보면 return
문의 인자가 여러 개이고, 함수를 호출한 쪽에선 success, msg
처럼 destructuring 하는 걸 볼 수 있습니다.
set 자료형
set
은 파이썬이 기본으로 제공해주는 기본 타입 중 하나입니다. list
타입과는 다르게 set
의 경우 종북된 원소를 허용하지 않습니다.
배열에서 중복된 원소를 제거하고 싶을 때 다음과 같이 사용할 수 있습니다.
arr = [1, 3, 5, 3, 1, 6, 3, 2]
print(arr) # [] 사용
s = set(arr) # 오브젝트 개념, 키가 set안에 존재하면 (값이) true, 없으면 false
print(s) # {} 사용
for n in s:
print(n)
참고로 set
타입을 print
함수를 이용해 출력하면 배열과 달리 {
와 }
로 감싸져있습니다.
이는 중복된 원소를 허용하지 않음을 나타냅니다.
참고로 set
자료형 역시 iterable이고, 아래와 같이 for-in
문의 오른쪽에 넣을 수 있습니다.
arr = [1, 3, 5, 3, 1, 6, 3, 2]
for n in set(arr):
print(n)
iterator - map
map
은 적용할 함수와, 원본 iterator를 받아서 각 원소에 제공된 함수를 적용한 뒤, 해당 함수의 반환값을 뱉는 iterator를 반환하는 함수입니다.
말로 하니까 좀 복잡한데, 아래 코드의 경우 각 원소에 1을 더한 iterator를 반혼합니다.
scores = [1, 6, 5, 6, 7, 8, 9, 3, 2, 1, 1]
def add_1(n):
return n + 1
m = map(add_1, scores)
참고로 iterator들은 일반적으로 print
할 경우 원소들의 값을 뱉지 않습니다.
원소들의 값을 출력할 떈 다음과 같이 list
로 감싸면 됩니다.
scores = [1, 6, 5, 6, 7, 8, 9, 3, 2, 1, 1]
def add_1(n):
return n + 1
m = map(add_1, scores)
print(list(m))