본 문서는 파이썬 코딩의 기술: Effective Python의 정리 내용입니다. 인용구문은 필자의 견해 또는 개인적인 궁금함을 담은 내용입니다.
namedtuple
을 통해 가벼운 데이터 컨테이너를 선언할 수 있다.파이썬의 hook
중 상당 수는 인수와 반환 값을 잘 정의해놓은상태가 없는
함수이다. 이는 파이썬의 함수는 일급객체이기 때문이다.
일급 객체 더 알아보기 - 1급 객체(First-class citizen) 란? with Kotlin
__call__
매직 메소드를 통해 클래스의 인스턴스를 함수처럼 호출 할 수 있다.
상태를 가지는 함수가 필요할 땐, 클로저 대신 __call__
메서드를 제공하는 클래스를 정의하는 방안을 고려하자
다른 언어에서 객체 생성 다형성을 생성자 다형성으로 해결하나, 파이썬은 단일 생성자 메서드 __init__
만을 허용한다. 이 문제를 해결하는 가장 좋은 방법은 @classmethod
다형성을 이용하는 것이다.
class GenericInputData(ABC):
@absractmethod
def read(self):
pass
@classmethod
def generate_inputs(self):
pass
class PathInputData(GenericInputData):
def __init__(self, path):
super().__init__()
self.path = path
def read(self):
return open(self.path).read()
@classmethod
def generate_inputs(cls, config)
data_dir = config['data_dir']
for name in os.listdir(data_dir):
yield cls(os.path.join(data_dir, name)
팩토리 패턴을 이용해 생성하는 것과 달리 위 방법은 인자를 받는 쪽에서 어떤 타입인지 몰라도 됨으로 더 유연한 구조를 가져 갈 수 있다.
__init__
메서드를 직접 호출하는 행위는 호출 순서가 모든 서브 클래스에 걸쳐 명시되어 있지 않으며, 다이아몬드 문제가 발생 할 수 있다.class BaseClass:
def __init__(self, value):
self.value = value
class TimesFive(BaseClass):
def __init__(self, value):
BaseClass.__init__(self, value)
self.value *= 5
class PlusTwo(BaseClass):
def __init__(self, value):
BaseClass.__init__(self, value)
self.value += 2
TimesFive
와PlusTwo
는 같은 부모 클래스를 상속받고 있다. 이 둘을 상속받는 클래스를 정의하여 BaseClass
를 다이아몬드 꼭대기로 만든다.
class Child(TimesFive, PlusTwo):
def __init__(self, value):
TimesFive.__init__(value)
PlusTwo.__init__(value)
print(Child(5).value)
>>> 7
25를 기대한 것과 달리 value
는 7을 가진다. 두번째 부모 클래스의 생성자 PlushTwo.__init__
에서 다시 BaseClass.__init__
를 호출하면서 value
를 5로 초기화 해놓기때문이다.
파이썬 2.2에서는 이러한 문제를 해결하고자super
라는 내장 함수를 추가하고 MRO, Method Resolution Order
를 정의했다. MRO
는 슈퍼 클래스 초기화 순서를 정하고, 다이아몬드 계층 구조에 공통 슈퍼클래스를 단 한번만 실행하게 한다.
MRO
순서는 mro()
라는 클래스 메소드로 알 수 있다. MRO
는 왼쪽 즉, 먼저 선언 될 수록 우선순위가 높고, 부모 클래스는 자식 클래스보다 나중에 실행된다.
class Nothing:
def __init__(self):
print('entry nothing')
print('exit nothing')
class MroTest(Nothing, TimesFive, PlusTwo):
def __init__(self, value):
super().__init__(value)
print(MroTest(5).value)
>>> Traceback (most recent call last):
File "main.py", line 52, in <module>
print(MroTest(5).value)
File "main.py", line 50, in __init__
super().__init__(value)
TypeError: __init__() takes 1 positional argument but 2 were given
위 코드는 에러가 발생하는데,MRO
에 따라 Nothing
의 생성자가 호출 되고, 더 이상 super()__init__
호출이 없기 때문에, 뒤 클래스 들에 생성자를 호출하지 못하고 종료되어 value
를 설정하지 못하고 끝나기 때문이다.
class Nothing:
def something(self):
print('something')
class MroTest(Nothing, TimesFive, PlusTwo):
def __init__(self, value):
super().__init__(value)
print(MroTest(5).value)
>>> 35
반면에Nothing
클래스의 생성자를 작성하지 않으면, MRO는 해당 클래스 생성자 호출을 생략하고, 다음 부모 클래스의 생성자를 호출함으로서, 정상적으로 동작한다. 이 때문에 생성자를 가지지 않는 MixIn
패턴은 비교적 상속문제에서 자유롭게 사용 할 수 있다.
__init__
생성자를 호출하도록 요구하지도 않는다.class Parent:
def __init__(self):
self.__private_field = 71
class Child(Parent):
def get_private_field():
return self.__private_field
print(Child().get_private_field())
>>> AttributeError
파이썬에서 비공개 속성의 동작은 간단하게 속성 이름을 변환하는 방식으로 구현된다. __
로 시작하는 비공개 속성은 실제로 _{클래스명}__{필드명}
형식으로 저장되어, 외부 클래스 또는 자식 클래스에서 부모의 비공개 속성에 접근하는 동작은 단순히 속성 이름이 일치하지 않아서 실패한다.
따라서 Child()._Parent__private_field
로 접근하면 접근이 가능하다.
비공개 속성을 선택하면 서브클래스의 오버라이드와 확장을 다루기 어렵고 불안정하게 만든다. 일반적으로 보호속성(_
로 시작하는)을 사용해서 서브 클래스가 더 많은 일을 할 수 있게 하는 편이 낫다.
비공개 속성을 사용하지 진지하게 고민할 시점은 서브 클래스와 이름이 충돌할 염려가 있을 때 뿐이다.
__getitem__
을 구현하면 된다. 그러나 len
,count
, index
등 다른 리스트의 일반적인 메소드는 구현되지 않았다.collections.abc
모듈은 각 컨테이너 타입에 필요한 일반적인 메서드를 모두 제공하는 추상 클래스들을 정의한다.__getitem__
, __len__
두 메서드만 정의하면 일반적인 메서드를 그대로 이용 할 수 있다.