2020년 2학기 파이썬 두번째 스터디 정리
5 min read
오늘은 클래스 사용법, 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))