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 발생을 출력합니다. ZeroDivisionErrorArithmeticError를 상속하기 때문인데,

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))