파이썬 코딩의 기술 책 5장 내용인 클래스와 인터페이스에 대해 리뷰를 하겠다. collections.abc와 믹스인에 대해서는 다루지 않겠다.
- 훅 , __call__
- 애트리뷰트
- @classmethod
- super
- 번외 - collections.abc
1. 훅
훅은 파이썬 내장 API를 호출할 때, 동작을 원하는 대로 바꿀 수 있는 함수이다. 예시를 보면 이해하기 쉬운데, 다음과 같이 sort에서 들어가는 len 함수가 훅이라고 할 수 있다.
tmpl = ['a','bb','ccc','abcd']
tmpl.sort(key = len) # len 이 훅!
또 다른 예시는 defaultdict에서 사용할 때다. defaultdict에서 딕셔너리가 변경될 때마다 print 하는 함수를 만들고, 이를 훅으로 사용할 수 있다.
from collections import defaultdict
def log_print():
print('revised')
return
dic = defaultdict(log_print, {}) # log_print가 훅!
dic['a'] = 1 # -> revised
dic['b'] = 2 # -> revised
위 defaultdict에서의 훅으로 작은 클래스를 만들어 사용할 수도 있다. defaultdict에 값이 추가될 때 마다 count를 증가시키는 훅을 만든다고 가정하자.
class Count:
def __init__(self):
self.added = 0 #이 added를 애트리뷰트라고 함
def missing(self):
self.added += 1
return
my_count = Count()
dic = defaultdict(my_count.missing, {})
이때 Count 클래스 내 __call__메서드를 사용하는 방법으로 바꿔줄 수 있다. 이 __call__은 객체를 함수처럼 호출할 수 있게 되기 때문에, 아래와 같이 코드를 생성할 수 있다.
class Count2:
def __init__(self):
self.added = 0
def __call__(self): #위 코드에서는 missing이었던 부분을 __call__로 치환
self.added += 1
return
my_count = Count2()
dic = defaultdict(my_count, {}) #callable 객체이기 때문에!
dic['a'] = 1
dic['b'] = 2
print(my_count.added) # -> 2 !
2. 애트리뷰트
훅 예제에서 만든 위 클래스에서는 self. 를 이용해 애트리뷰트를 만들었는데 이름의 유형을 간단 정리하면 아래와 같다.
self.added | self.__added |
공개 애트리뷰트 | 비공개 애트리뷰트 밑줄 두개, 비공개 필드 클래스 외부에서 접근 X |
비공개 애트리뷰트는 자식 클래스에서도 (원래는) 접근을 못한다.
class Myclass:
def __init__(self):
self.__added = 1
class MyChildclass(Myclass):
def get_added(self):
return self.__added
그 이유는 파이썬 컴파일러가 비공개 애트리뷰트에 접근할 때 다음과 같은 접근 코드를 쓰기 때문이다. 자식 클래스 이름이 코드로 들어가기 때문에, 부모 클래스의 __added를 접근하지 못하는 것이다. 즉, __added의 접근 이름은 Myclass__added이란 것!
#하위 클래스에서 비공개 어트리뷰트 접근할 때
c = MyChildclasss()
c.get_added() #-> error! 접근 코드 : _MyChildclass__added
그렇기 때문에 접근 코드 이름을 하위 클래스인 _MyChildclass가 아닌 부모 클래스로 바꿔주면 된다.
assert c._Myclass__added == 1
3. @classmethod
cf.) 다형성 : 클래스가 자신에게 맞는 유일한 메서드 버전을 구현할 수 있다. 이를 활용하면 하위 클래스 객체를 만들거나 연결할 수 있는 제너릭 방법을 사용할 수 있다.
class Myclass:
@classmethod # 클래스매서드 정의
def generate_inputs(cls, config):
# 파라미터인 cls, config는 하위클래스의 파라미터
# 새로운 Myclass 인스턴스를 생성
raise NotImplementedError
class Subclass(Myclass): #Myclass의 하위클래스
#...
def generate_inputs(cls, config):
#...
위 예시코드에서 cls()는 제너릭 생성자로 작용한다. 간단하게 말하자면 self로 메서드를 호출하는 대신 cls을 사용하여 접근하는 것이다.
그렇다면 어떤 경우에 굳이 왜 인스턴스 매서드(self로 접근할 수 있었던)가 아닌 클래스 메서드를 사용하는 걸까? 바로 다형성과 제너릭이다.
클래스 상속으로 다형성을 구현할 수는 있지만, 이 방법은 제너릭(타입에 상관없이 작동하는)하지 않다. 이를 위해 클래스 메서드 다형성을 사용해 클래스 전체에 적용한다.
4. super
cf.) __init__ : 이 메서드는 부모 클래스를 초기화하기 위해 자식클래스에서 쓰는 매서드이다. 참고로 파이썬에 있는 유일한 생성자 매서드다. 부모 클래스가 여러 개면 이 init의 작동 순서가 정해져 있지 않기 때문에 문제가 생길 수 있다.
class Myclass:
def __init__(self, value):
self.value = value
class Myclass1:
def __init__(self):
self.value += 5
class Myclass2:
def __init__(self):
self.value *= 5
class Subclass(Myclass, Myclass1, Myclass2): #부모클래스 3개를 상속
def __init__(self, value):
Myclass.__init__(self, value)
Myclass1.__init__(self)
Myclass2.__init__(self)
sub = Subclass(value = 3) # 과연 self.value의 값이 어떻게 나올까?
super는 pytorch를 사용하는 사람들은 많이 봤을 법한 내장 함수로, 생성자인 __init__ 함수 아래에 사용하는 방법이다. super는 여러개 클래스를 상속받는 상황일 때 상위 클래스는 한 번만 호출하게끔 보장한다. 이에 따라 위 코드는 아래와 같이 다시 작성할 수 있다.
class Myclass:
def __init__(self, value):
self.value = value
class Myclass1(Myclass):
def __init__(self, value):
super().__init__(value)
self.value += 5
class Myclass2(Myclass):
def __init__(self, value):
super().__init__(value)
self.value *= 5
class SuperSubclass(Myclass1, Myclass2): #부모클래스 2개 + 최상위클래스 1개를 상속
def __init__(self, value):
super().__init__(value)
sub2 = SuperSubclass(value = 3) # 3 * 5 + 5 == 20
mro_str = '-> '.join(repr(cls) for cls in SuperSubclass.mro())
print(mro_str) # <class '__main__.SuperSubclass'>-> <class '__main__.Myclass1'>-> <class '__main__.Myclass2'>-> <class '__main__.Myclass'>-> <class 'object'>
참고로 super init 호출 순서는 MRO 정의를 따르는데, 이 MRO는 최상위 클래스부터 object 까지의 순서로 호출하지만, 작업은 호출된 순서의 역순으로 가게 된다. 즉, Myclass, Myclass2, Myclass1 순서로 실제 value가 계산되는 것이다.
super 안 파라미터는 2개를 받을 수 있는데, 첫번째는 접근하고 싶은 MRO 뷰를 제공할 부모 타입(주로 내가 부를 클래스의 이름을 적는다), 다른 하나는 방금 지정한 MRO 뷰에 접근할 때 사용할 인스턴스(self)이다. 그러면 아래와 같이 SuperSubclass를 다시 정의할 수 있다.
class SuperSubclass(Myclass1, Myclass2): #부모클래스 3개를 상속
def __init__(self, value):
super(SuperSubclass, self).__init__(value)
# SuperSubclass : MRO 뷰를 제공할 부모 타입
# self : 위에 선언한 MRO 뷰 제공 타입에 접근할 인스턴스
sub2 = SuperSubclass(value = 3)
5. 번외
cf.) collection.abc : 커스텀 컨테이너 타입에 잘 메서드가 구현되어 있는지 확인하고, 실수한 부분을 알려주는 모듈이다. 아래와 같이 불러올 수 있다.
from collections.abc import Sequence
class Myclass(Sequence):
...
이번에는 클래스를 활용할 때 사용할 수 있는 여러 기능에 대해 알아보았다. 훅 함수의 제대로 된 정의, 공개 애트리뷰트와 비공개 애트리뷰트의 차이, 다형성을 위한 @classmethod로 제너릭을 부여하는 올바른 사용법과 ML 엔지니어라면 친숙할 super 에 대해 기술했다. 코드를 잘 짜는 ML 엔지이너가 되기, 힘내자! 공부한 모든 것을 실전에서 잘 활용할 수 있기를 바라며.
'python > 파이썬 코딩의 기술' 카테고리의 다른 글
파이썬 리스트 append 연산 시 arr와 arr[:] 차이점 (0) | 2022.02.12 |
---|---|
파이썬 코딩의 기술 리뷰 - 메타클래스와 애트리뷰트 1 (0) | 2022.02.01 |
파이썬 코딩의 기술 리뷰- 동시성과 병렬성 1 (2) | 2022.01.10 |
파이썬 코딩의 기술 - 컴프리헨션, 제너레이터 (0) | 2022.01.09 |
파이썬 코딩의 기술 리뷰 - 함수 (0) | 2021.12.19 |