Search

파이썬 Container, Dynamic, and Callable Object

생성일
2023/04/04 05:28
태그
python
날짜
2023/04/05
3 more properties

개요

앞선 포스팅에서 파이썬의 여러 매직메서드에 대해 살펴보았다. 오늘은 이어서 특정 매직매서드를 구현하여 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
복사