# 2020년 2학기 파이썬 두번째 스터디 정리



오늘은 클래스 사용법, `raise` 사용법, exception 처리, tuple 자료형, set 자료형을 다루었고, iterator와 관련된 `map` 함수도 짧게 다루었습니다.

# 클래스

클래스를 선언하는 방법은 가장 간단한 방법은 `pass`를 사용하는 것입니다.

```python
class Car:
    pass
```

이렇게 선언한 클래스는

```python
car = Car()
```

처럼 초기화할 수 있습니다. 그리고 `pass`를 사용한 선언은 아래 코드와 똑같다고 보시면 됩니다.

```python
class Car:
    def __init__(self):
        pass
```

아래와 같은 방식으로 클래스에 메소드를 선언하고 사용할 수 있습니다.

```python
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`이라는 프로퍼티도 정의하지 않습니다.

```python
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`이라는 클래스를 상속하기 때문입니다.

## 오버로딩

```python
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 호출

메소드를 오버라이딩한 경우에도 아래처럼 부모 클래스의 함수를 호출할 수 있습니다.

```python

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으로 나눌 수 없습니다`라는 에러를 던지는 함수를 파이썬으로 구현하면 다음과 같습니다.

```python

def divide(a, b):
    if b == 0:
        raise ArithmeticError('0으로 나눌 수 없습니다')

    return a // b

```

# 예외 처리

프로그램이 실행되다보면 많은 예외 케이스가 있습니다. 와이파이가 끊긴다던가 input 파일이 없다던가...

```python
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`문이 먼저 와야 한다는 점입니다.

```python
try:
    res = 5 // 0
    # 순서 중요 (클래스 상속)
except ZeroDivisionError as e:
    print('ZeroDivisionError 발생')
except ArithmeticError as e:
    print('ArithmeticError 발생')

```

위 코드는 `ZeroDivisionError 발생`을 출력합니다. 하지만

```python
try:
    res = 5 // 0
    # 순서 중요 (클래스 상속)
except ArithmeticError as e:
    print('ArithmeticError 발생')
except ZeroDivisionError as e:
    print('ZeroDivisionError 발생')

```

위 코드는 `ArithmeticError 발생`을 출력합니다. `ZeroDivisionError`이 `ArithmeticError`를 상속하기 때문인데,

```python
except ZeroDivisionError as e:
    print('ZeroDivisionError 발생')
```

이 부분이 실행되지 않는 dead code가 됩니다

전혀 다른 오류인 경우 순서가 중요하지 않습니다.

```python
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 타입은 상당히 편리합니다.

```python

arr = [1, 2, 3, 4, 5]

# 주의: 배열의 길이가 왼쪽의 변수 개수와 같아야 함
a, b, _, c, _ = arr

print(a)
print(b)
print(c)
```

위 코드는

```
1
2
4
```

를 출력합니다.

## tuple 반환

파이썬에서는 함수에서 tuple 자료형을 반환할 수 있습니다.

```python
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`의 경우 종북된 원소를 허용하지 않습니다.
배열에서 중복된 원소를 제거하고 싶을 때 다음과 같이 사용할 수 있습니다.

```python

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` 문의 오른쪽에 넣을 수 있습니다.

```python

arr = [1, 3, 5, 3, 1, 6, 3, 2]
for n in set(arr):
    print(n)
```

# iterator - map

`map`은 적용할 함수와, 원본 iterator를 받아서 각 원소에 제공된 함수를 적용한 뒤, 해당 함수의 반환값을 뱉는 iterator를 반환하는 함수입니다.

말로 하니까 좀 복잡한데, 아래 코드의 경우 각 원소에 1을 더한 iterator를 반혼합니다.

```python
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`로 감싸면 됩니다.

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

