[파이썬 코딩의 기술] 클래스

피누·2020년 10월 10일
0

본 문서는 파이썬 코딩의 기술: Effective Python의 정리 내용입니다. 인용구문은 필자의 견해 또는 개인적인 궁금함을 담은 내용입니다.

Better Way 22 - 딕셔너리와 튜플보다는 헬퍼 클래스로 관리하자

  • 딕셔너리를 담는 딕셔너리는 최대한 피하자. 여러 계층이 중첩되면 코드 유지보수가 어려워진다. 관리가 복잡하다고 느끼는 즉시 클래스로 옮겨가야 한다.
  • namedtuple 을 통해 가벼운 데이터 컨테이너를 선언할 수 있다.

Better Way 23 - 인터페이스가 간단하면 클래스 대신 함수를 받자

  • 파이썬의 hook 중 상당 수는 인수와 반환 값을 잘 정의해놓은상태가 없는 함수이다. 이는 파이썬의 함수는 일급객체이기 때문이다.

    일급 객체 더 알아보기 - 1급 객체(First-class citizen) 란? with Kotlin

  • __call__ 매직 메소드를 통해 클래스의 인스턴스를 함수처럼 호출 할 수 있다.

  • 상태를 가지는 함수가 필요할 땐, 클로저 대신 __call__ 메서드를 제공하는 클래스를 정의하는 방안을 고려하자

Better Way 24 - 객체를 범용으로 생성하려면 @classmethod 다형성을 이용하자

다른 언어에서 객체 생성 다형성을 생성자 다형성으로 해결하나, 파이썬은 단일 생성자 메서드 __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)   
  • 구체 서브클래스들을 만들고 연결하는 범용적인 방법을 제공하려면 클래스 메서드 다형성을 이용하자

    팩토리 패턴을 이용해 생성하는 것과 달리 위 방법은 인자를 받는 쪽에서 어떤 타입인지 몰라도 됨으로 더 유연한 구조를 가져 갈 수 있다.

Better Way 25 - super로 부모 클래스를 초기화하자

  • 슈퍼클래스의 __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

TimesFivePlusTwo는 같은 부모 클래스를 상속받고 있다. 이 둘을 상속받는 클래스를 정의하여 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 패턴은 비교적 상속문제에서 자유롭게 사용 할 수 있다.

Better Way 26 - 믹스인 유틸리티 클래스에만 다중 상속을 사용하자

  • 다중 상속으로 얻는 편리함과 캡슐화가 필요하다면 대신 믹스인을 사용하자. 믹스인이란 클래스에서 제공해야하는 추가적인 메서드만 정의하는 작은 클래스를 말한다. 믹스인 클래스는 자체의 인스턴스 속성을 정의하지 않으며 __init__ 생성자를 호출하도록 요구하지도 않는다.
  • 인스턴스 수준에서 동작을 교체할 수 있게 만들어서 믹스인 클래스가 요구할 때 클래스 별로 원하는 동작을 하게 하자.
  • 간단한 동작들로 복잡한 기능을 생성하려면 믹스인을 조합하자.

Better Way 27 - 공개 속성보다는 비공개 속성을 사용하자

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로 접근하면 접근이 가능하다.

비공개 속성을 선택하면 서브클래스의 오버라이드와 확장을 다루기 어렵고 불안정하게 만든다. 일반적으로 보호속성(_로 시작하는)을 사용해서 서브 클래스가 더 많은 일을 할 수 있게 하는 편이 낫다.

비공개 속성을 사용하지 진지하게 고민할 시점은 서브 클래스와 이름이 충돌할 염려가 있을 때 뿐이다.

Better Way 28 - 커스텀 컨테이너 타입은 collection.abc의 클래스를 상속받게 만들자

  • 시퀀스처럼 인덱스 접근을 허용하고자 한다면 __getitem__을 구현하면 된다. 그러나 len,count, index 등 다른 리스트의 일반적인 메소드는 구현되지 않았다.
  • 위와 같은 어려움을 피하고자 파이썬의 내장 collections.abc 모듈은 각 컨테이너 타입에 필요한 일반적인 메서드를 모두 제공하는 추상 클래스들을 정의한다.
  • 시퀀스 타입은 __getitem__, __len__ 두 메서드만 정의하면 일반적인 메서드를 그대로 이용 할 수 있다.
profile
어려운 문제를 함께 풀어가는 것을 좋아합니다.

0개의 댓글