weakref — 약한 참조
weakref — 약한 참조 (Weak references)
weakref 모듈은 Python 프로그래머가 객체에 대한 약한 참조(weak references)를 만들 수 있게 해줘요. 아래에서 "referent"라는 용어는 약한 참조가 가리키는 객체를 의미해요.
객체에 대한 약한 참조만으로는 객체를 살아있게 유지하기에 충분하지 않아요. referent에 남은 참조가 약한 참조뿐이면 가비지 컬렉션이 referent를 파괴하고 그 메모리를 다른 용도로 재사용할 수 있어요. 다만 객체가 실제로 파괴되기 전까지는 강한 참조가 없어도 약한 참조가 객체를 반환할 수 있어요.
본문
약한 참조의 주요 용도 중 하나는 큰 객체를 보관하는 캐시나 매핑을 구현하는 것이에요. 이때 큰 객체가 캐시나 매핑에 나타난다는 이유만으로 살아있게 유지되지 않기를 원해요.
예를 들어 많은 큰 바이너리 이미지 객체가 있고 각각에 이름을 연결하고 싶다고 해요. 이름을 이미지로, 또는 이미지를 이름으로 매핑하는 Python dict를 사용하면 이미지 객체가 사전의 값이나 키로 나타난다는 이유만으로 살아있게 유지돼요. WeakKeyDictionary와 WeakValueDictionary 클래스는 약한 참조를 사용해 객체가 매핑에 나타난다는 이유만으로 살아있게 유지하지 않는 매핑을 만들 수 있는 대안이에요.
WeakKeyDictionary와 WeakValueDictionary는 구현에서 약한 참조를 사용하고, 키나 값이 가비지 컬렉션에 의해 회수될 때 약한 사전에 알리는 약한 참조의 콜백 함수를 설정해요. WeakSet은 set 인터페이스를 구현하지만 요소에 약한 참조를 유지해요.
finalize는 객체가 가비지 컬렉션될 때 호출할 정리(cleanup) 함수를 등록하는 간단한 방법을 제공해요. 원시 약한 참조에 콜백 함수를 설정하는 것보다 사용하기 간단해요.
대부분의 프로그램은 이러한 약한 컨테이너 타입 중 하나나 finalize를 사용하는 것으로 충분해요. 모든 객체가 약한 참조될 수 있는 것은 아니에요. 약한 참조를 지원하는 객체에는 클래스 인스턴스, Python으로 작성된 함수, 인스턴스 메서드, set, frozenset, 일부 파일 객체, 제너레이터, 타입 객체, 소켓, 배열, deque, 정규 표현식 패턴 객체, 코드 객체가 포함돼요.
list와 dict 같은 여러 내장 타입은 약한 참조를 직접 지원하지 않지만 하위 클래스화를 통해 지원을 추가할 수 있어요. tuple과 int 같은 다른 내장 타입은 하위 클래스화해도 약한 참조를 지원하지 않아요. __slots__가 정의된 타입은 __slots__ 선언의 문자열 시퀀스에 '__weakref__' 문자열도 있지 않으면 약한 참조 지원이 비활성화돼요.
약한 참조 객체 (Weak Reference Objects)
classweakref.ref(object[, callback])
object에 대한 약한 참조를 반환해요. 원본 객체는 referent가 여전히 살아있으면 참조 객체를 호출해 검색할 수 있고, referent가 더 이상 살아있지 않으면 참조 객체를 호출하면 None이 반환돼요. callback이 제공되고 None이 아니고, 반환된 weakref 객체가 여전히 살아있으면 객체가 finalize되려 할 때 콜백이 호출돼요. 약한 참조의 콜백이 일으킨 예외는 표준 오류 출력에 기록되지만 전파될 수는 없어요.
약한 참조는 객체가 해시 가능하면 해시 가능해요. 약한 참조는 동등성 테스트를 지원하지만 순서는 지원하지 않아요. 이것은 팩토리 함수가 아니라 하위 클래스화 가능한 타입이에요.
약한 참조는 참조하는 객체의 타입에 대해 제네릭이에요.
__callback__— 현재 weakref에 연결된 콜백을 반환하는 읽기 전용 속성
weakref.proxy(object[, callback])
약한 참조를 사용하는 object에 대한 프록시를 반환해요. 프록시는 약한 참조 객체에서 사용되는 명시적 역참조 대신 대부분의 상황에서 프록시 사용을 지원해요. 반환된 객체는 object가 호출 가능한지에 따라 ProxyType 또는 CallableProxyType 타입이에요. 프록시 객체는 referent에 관계없이 해시 가능하지 않아요. referent가 가비지 컬렉션된 후 프록시 객체의 속성에 접근하면 ReferenceError가 발생해요.
weakref.getweakrefcount(object) / weakref.getweakrefs(object)
object를 가리키는 약한 참조와 프록시 수 / 목록을 반환해요.
약한 컨테이너 (Weak Containers)
classweakref.WeakKeyDictionary([dict])
키를 약하게 참조하는 매핑 클래스예요. 키에 대한 강한 참조가 더 이상 없으면 사전의 항목이 버려져요. 이미 존재하는 키와 값은 같지만 동일성(identity)은 다른 키가 삽입되면 값을 교체하지만 기존 키는 교체하지 않아요. |와 |= 연산자를 지원해요. 키에 대한 약한 참조의 iterable을 반환하는 추가 메서드 keyrefs()가 있어요.
classweakref.WeakValueDictionary([dict])
값을 약하게 참조하는 매핑 클래스예요. 값에 대한 강한 참조가 더 이상 없으면 사전의 항목이 버려져요. 값에 대한 약한 참조의 iterable을 반환하는 추가 메서드 valuerefs()가 있어요.
classweakref.WeakSet([elements])
요소에 약한 참조를 유지하는 Set 클래스예요. 요소에 대한 강한 참조가 더 이상 없으면 버려져요.
classweakref.WeakMethod(method[, callback])
바운드 메서드(클래스에 정의되고 인스턴스에서 조회된 메서드)에 대한 약한 참조를 시뮬레이션하는 사용자 정의 ref 하위 클래스예요. 바운드 메서드는 일시적이므로 표준 약한 참조는 그것을 붙잡을 수 없어요. WeakMethod는 객체나 원래 함수가 죽을 때까지 바운드 메서드를 다시 만드는 특수 코드를 가져요.
classweakref.finalize(obj, func, /, *args, **kwargs)
obj가 가비지 컬렉션될 때 호출될 호출 가능 finalizer 객체를 반환해요. 일반 약한 참조와 달리 finalizer는 참조 객체가 수집될 때까지 항상 살아남아 수명 주기 관리를 크게 단순화해요. __call__(), detach(), peek() 메서드와 alive, atexit 속성이 있어요. 프로그램이 종료될 때(인터프리터 종료 시) 남은 살아있는 각 finalizer는 생성의 역순으로 호출돼요. func, args, kwargs가 obj에 대한 참조를 직접 또는 간접적으로 소유하지 않도록 보장하는 것이 중요해요.
타입 객체
- classweakref.ReferenceType — 약한 참조 객체의 타입 객체
- classweakref.ProxyType — 호출 가능하지 않은 객체 프록시의 타입 객체
- classweakref.CallableProxyType — 호출 가능한 객체의 프록시 타입 객체
- weakref.ProxyTypes — 프록시의 모든 타입 객체를 담는 시퀀스
약한 참조 객체 사용
약한 참조 객체는 ref.__callback__ 외에 메서드와 속성이 없어요. referent가 여전히 존재하면 참조 객체를 호출해 얻을 수 있어요:
>>> import weakref
>>> class Object:
... pass
>>> o = Object()
>>> r = weakref.ref(o)
>>> o2 = r()
>>> o is o2
True
referent가 더 이상 존재하지 않으면 참조 객체를 호출하면 None이 반환돼요. 약한 참조 객체가 여전히 살아있는지 테스트하려면 ref() is not None 표현식을 사용해야 해요. 스레드 애플리케이션에서 liveness에 대한 별도 테스트를 사용하면 약한 참조가 호출되기 전에 다른 스레드가 무효화시킬 수 있으므로 경쟁 조건이 생겨요. 위 관용구는 스레드 애플리케이션에서도 안전해요.
finalize 사용 예제
finalize를 사용하는 주요 이점은 반환된 finalizer 객체를 보존할 필요 없이 콜백을 등록하기 간단하다는 것이에요:
>>> import weakref
>>> class Object:
... pass
>>> kenny = Object()
>>> weakref.finalize(kenny, print, "You killed Kenny!")
<finalize object at ...; for 'Object' at ...>
>>> del kenny
You killed Kenny!
finalizer와 del() 메서드 비교
__del__() 메서드의 처리는 인터프리터의 가비지 컬렉터 구현의 내부 세부 사항에 의존하므로 악명 높게 구현별로 다릅니다. 더 견고한 대안은 객체의 전체 상태에 접근하는 대신 특정 함수와 객체만 참조하는 finalizer를 정의하는 것이에요:
class TempDir:
def __init__(self):
self.name = tempfile.mkdtemp()
self._finalizer = weakref.finalize(self, shutil.rmtree, self.name)
def remove(self):
self._finalizer()
@property
def removed(self):
return not self._finalizer.alive
weakref 기반 finalizer의 또 다른 장점은 정의가 서드파티에 의해 제어되는 클래스의 finalizer를 등록하는 데 사용할 수 있다는 것이에요.