개요
앞선 포스팅에서 파이썬의 여러 매직메서드에 대해 살펴보았다. 오늘은 이어서 특정 매직매서드를 구현하여 Container, Dynamic Property, Callable에 대해 살펴보고 파이썬스럽게 코드를 작성하는 것에 대해 살펴보자. 해당 포스트를 읽고 나서 조금은 더 클린하고, 유연한 코드를 작성할 수 있기를 바란다.
Container Object
파이썬에서 Container 객체란 __contains__ 메서드를 구현한 객체를 의한다. 이 메서드는 보통 Boolean 값을 리턴하며 추상적으로 포함(contain)하는 지와 관련이 있다. 파이썬은 in 키워드를 인식하면 객체의 __contains__ 메서드를 호출한다.
container = [1, 2, 3]
element = 2
print(element in container) # True
# this is the same as the code below
print(container.__contains__(element)) # True
Python
복사
Container 객체를 활용하여 가독성 늘리기
2차원의 grid에 좌표를 찍는 경우를 생각해보자. 이 때, mark를 찍는 위치가 grid 안에 있어야만 작동될 것이다. 이러한 경우 아래와 같이 코드를 작성할 수 있다.
def mark_coordinate(grid, coord):
if 0 <= coord.x < grid.width and 0 <= coord.y < grid.height:
grid[coord] = MARKED
Python
복사
하지만 container 속성을 잘 이용하면 훨씬 파이썬스러운 코드를 작성할 수 있다.
class Boundaries:
def __init__(self, width, height):
self.width = width
self.height = height
def __contains__(self, coord):
x, y = coord
return 0 <= x < self.width and 0 <= y < self.height
class Grid:
def __init__(self, width, height):
self.width = width
self.height = height
self.limits = Boundaries(width, height)
def __contains__(self, coord):
return coord in self.limits
Python
복사
Dynamic Property of an Object
__getattr__
__getattr__ 메서드를 잘 작성하면 파이썬에서 객체의 속성을 접근하는 과정을 컨트롤 할 수 있다. <myobject>.<myattribute>를 호출하게 되면 파이썬에서 다음과 같은 작업을 하게 된다.
1.
객체의 __dict__ 딕셔너리에서 <myattribute>가 있는 지 확인하고 있다면 찾은 객체의 __getattribute__ 메서드를 호출한다.
2.
만약 1번에서 찾지 못했다면 객체의 __getattr__ 메서드를 호출한다.
따라서 __getattr__ 메서드를 잘 구현하면 존재하지 않는 속성에 접근하는 경우를 처리할 수 있게 된다.
class DynamicAttributes:
def __init__(self, attribute):
self.attribute = attribute
def __getattr__(self, attr):
if attr.startswith("fallback_"):
name = attr.replace("fallback_", "")
return f"[fallback resolved] {name}"
raise AttributeError(f"{self.__class__.__name__} has no {attr} attribute")
Python
복사
__getattr__ 작동 테스트
dyn = DynamicAttributes("value")
Python
복사
# test 1: 객체에 속성이 존재하는 경우
print(dyn.attribute) # value
Python
복사
# test 2: 객체에 속성이 존재하지 않는 경우
# dyn.__getattr__("fallback_test") 이 호출된다
print(dyn.fallback_test) # [fallback resolved] test
Python
복사
# test case 3
# dyn attribute 정보에 직접적으로 접근한다
# 주의) __getattr__가 호출되지 않는다
dyn.__dict__["fallback_new"] = "new value"
print(dyn.fallback_new) # new value
Python
복사
# test case 4
# __getattr__에서 raise AttributeError를 했다는 사실을 기억하자
# getattr 내장함수는 해당 예외가 발생하면 default 값을 반환한다
# 코드가 clean and consistent하다
print(getattr(dyn, "something", "default")) # default
print(getattr(dyn, "fallback_sth", "default")) # [fallback resolved] sth
Python
복사
+Playground
dyn = DynamicAttributes("value")
print(dyn.__getattribute__) # <method-wrapper '__getattribute__' of ...
print(dyn.__getattr__) # <bound method DynamicAttributes.__getattr__ of ...
Python
복사
+__setattr__
__getattr__와 비슷하게 __setattr__ 메서드를 구현해서 attribute를 할달할 때를 관리할 수도 있다. 이 부분의 메커니즘은 __getattr__과 크게 다르지 않으니 read-only 속성을 만드는 예시를 보고 넘어가자.
class DynamicAttributes:
def __init__(self, attribute):
self.attribute = attribute
def __getattr__(self, attr):
if attr.startswith("fallback_"):
name = attr.replace("fallback_", "")
return f"[fallback resolved] {name}"
raise AttributeError(f"{self.__class__.__name__} has no {attr} attribute")
def __setattr__(self, attr, value):
if attr.startswith("readonly_"):
raise AttributeError(f"{attr} is read-only")
super().__setattr__(attr, value)
Python
복사
dyn = DynamicAttributes("value")
dyn.test = 1
print(dyn.test) # 1
dyn.readonly_test = 2 # AttributeError: readonly_test is read-only
Python
복사
Callable Object
대표적인 예로 데코레이터가 있다. 이는 추후에 다룰 예정이다.
파이썬은 object(*args, **kwargs) 구문을 object.__call__(*args, **kwargs)로 변환한다. 따라서 __call__ 메서드를 구현하므로써 호출형 객체를 다룰 수 있게 된다.
class SayHello:
def __init__(self):
self._call_cnt = 0
def __call__(self, name):
self._call_cnt += 1
print(f'hi {name}! you are {self._call_cnt}th visitor!')
greet = SayHello()
greet("Lee")
greet("Yang")
greet("Kim")
Python
복사
# console
hi Lee! you are 1th visitor!
hi Yang! you are 2th visitor!
hi Kim! you are 3th visitor!
Python
복사