weakref --- 약한 참조

소스 코드: Lib/weakref.py


weakref 모듈은 파이썬 프로그래머가 객체에 대한 약한 참조 (weak references)를 만들 수 있도록 합니다.

이하에서, 용어 참조대상(referent)은 약한 참조로 참조되는 객체를 의미합니다.

객체에 대한 약한 참조만으로는 객체를 살아있게 유지할 수 없습니다: 참조대상에 대한 유일한 남은 참조가 약한 참조면, 가비지 수거는 자유롭게 참조대상을 파괴하고 메모리를 다른 용도로 재사용할 수 있습니다. 그러나 객체가 실제로 파괴될 때까지 약한 참조는 강한 참조가 없어도 객체를 반환할 수 있습니다.

약한 참조의 주요 용도는 큰 객체를 보유하는 캐시나 매핑을 구현하는 것입니다. 큰 객체는 캐시나 매핑에 등장한다는 이유만으로 살아 있지 않아야 합니다.

예를 들어, 큰 바이너리 이미지 객체가 여러 개 있을 때, 이름을 각 객체와 연관 지을 수 있습니다. 파이썬 딕셔너리를 사용하여 이름을 이미지에 매핑하거나 이미지를 이름에 매핑하면, 이미지 객체는 딕셔너리에 값이나 키로 등장하기 때문에 계속 살아있게 됩니다. weakref 모듈에서 제공하는 WeakKeyDictionaryWeakValueDictionary 클래스는 대안이며, 약한 참조를 사용하여 매핑을 구축하기 때문에 매핑 객체에 등장한다는 이유만으로 객체를 살려두지 않습니다. 예를 들어 이미지 객체가 WeakValueDictionary 의 값이면, 해당 이미지 객체에 대한 마지막 남은 참조가 약한 매핑에 들어 있는 약한 참조이면, 가비지 수거는 객체를 회수할 수 있으며, 약한 매핑의 해당 항목은 간단히 삭제됩니다.

WeakKeyDictionaryWeakValueDictionary는 구현에 약한 참조를 사용하여, 가비지 수거에서 키나 값이 회수될 때, 약한 딕셔너리에 알리는 약한 참조에 대한 콜백 함수를 설정합니다. WeakSetset 인터페이스를 구현하지만, WeakKeyDictionary 처럼 원소에 대한 약한 참조를 유지합니다.

finalize는 객체가 가비지 수거될 때 호출될 정리 함수를 등록하는 간단한 방법을 제공합니다. 이 모듈은 원시 약한 참조에 콜백 함수를 설정하는 것보다 사용하기가 더 쉽습니다. 모듈은 객체가 수거될 때까지 자동으로 파이널라이저가 활성 상태로 유지되도록 하기 때문입니다.

대부분의 프로그램은 이러한 약한 컨테이너형이나 finalize를 사용하는 것으로 충분합니다 -- 일반적으로 여러분 스스로 약한 참조를 직접 만들 필요는 없습니다. 저수준 장치는 고급 용도를 위해 weakref 모듈이 노출합니다.

모든 객체를 약하게 참조할 수 있는 것은 아닙니다; 가능한 객체에는 클래스 인스턴스, 파이썬으로 작성된 함수 (C로 작성된 함수는 아닙니다), 인스턴스 메서드, 집합, 불변 집합(frozenset), 일부 파일 객체, 제너레이터, 형 객체, 소켓, 배열, 데크(deque), 정규식 패턴 객체 및 코드 객체가 포함됩니다.

버전 3.2에서 변경: thread.lock, threading.Lock 및 코드 객체에 대한 지원이 추가되었습니다.

listdict와 같은 여러 내장형은 약한 참조를 직접 지원하지 않지만, 서브 클래싱을 통해 지원을 추가할 수 있습니다:

class Dict(dict):
    pass

obj = Dict(red=1, green=2, blue=3)   # 이 객체는 약한 참조할 수 있습니다

CPython implementation detail: tupleint와 같은 다른 내장형은 서브 클래싱 될 때도 약한 참조를 지원하지 않습니다.

확장형은 쉽게 약한 참조를 지원하도록 만들 수 있습니다; 약한 참조 지원을 참조하십시오.

class weakref.ref(object[, callback])

object에 대한 약한 참조를 반환합니다. 참조대상이 아직 살아있으면 참조 객체를 호출하여 원래 객체를 얻을 수 있습니다. 참조대상이 더는 존재하지 않으면 참조 객체를 호출할 때 None이 반환됩니다. None이 아닌 callback이 제공되고, 반환된 약한 참조 객체가 여전히 살아있으면, 객체가 파이널라이즈 되려고 할 때 콜백이 호출됩니다; 약한 참조 객체는 콜백에 유일한 매개 변수로 전달됩니다; 참조대상은 더는 사용할 수 없습니다.

같은 객체에 대해 여러 개의 약한 참조를 구성할 수 있습니다. 각 약한 참조에 등록된 콜백은 가장 최근에 등록된 콜백에서 가장 오래전에 등록된 콜백 순으로 호출됩니다.

콜백에 의해 발생한 예외는 표준 에러 출력에 표시되지만, 전파될 수는 없습니다; 객체의 __del__() 메서드에서 발생한 예외와 정확히 같은 방식으로 처리됩니다.

약한 참조는 object가 해시 가능하면 해시 가능입니다. object가 삭제된 후에도 해시값을 유지합니다. 오직 object가 삭제된 후에 hash()를 처음 호출하면, 호출은 TypeError를 발생시킵니다.

약한 참조는 동등 검사를 지원하지만, 순서는 지원하지 않습니다. 참조대상이 여전히 살아 있다면, 두 참조는 (callback과 관계없이) 참조대상과 같은 동등 관계를 갖습니다. 참조대상이 삭제되었으면, 참조 객체가 같은 객체일 때만 참조가 같습니다.

이것은 팩토리 함수가 아니라 서브 클래싱 할 수 있는 형입니다.

__callback__

이 읽기 전용 어트리뷰트는 현재 약한 참조와 연관된 콜백을 반환합니다. 콜백이 없거나 약한 참조의 참조대상이 더는 살아있지 않으면 이 어트리뷰트의 값은 None이 됩니다.

버전 3.4에서 변경: __callback__ 어트리뷰트를 추가했습니다.

weakref.proxy(object[, callback])

object로의 약한 참조를 사용하는 프락시를 반환합니다. 이는 약한 참조 객체에서 사용되는 명시적 역참조를 요구하는 대신 대부분의 문맥에서 프락시의 사용을 지원합니다. 반환된 객체는 object가 콜러블인지에 따라 ProxyType이나 CallableProxyType 형을 갖습니다. 프락시 객체는 참조대상에 관계없이 해시 가능하지 않습니다; 이것은 그들의 근본적인 가변 특성과 관련된 여러 가지 문제를 피하고, 딕셔너리 키로 사용하는 것을 방지합니다. callbackref() 함수의 같은 이름의 매개 변수와 같습니다.

버전 3.8에서 변경: 행렬 곱셈 연산자 @@=을 포함하도록 프락시 객체에 대한 연산자 지원을 확장했습니다.

weakref.getweakrefcount(object)

object를 참조하는 약한 참조와 프락시의 개수를 반환합니다.

weakref.getweakrefs(object)

object를 참조하는 모든 약한 참조와 프락시 객체의 리스트를 반환합니다.

class weakref.WeakKeyDictionary([dict])

키를 약하게 참조하는 매핑 클래스. 더는 키에 대한 강한 참조가 없으면 딕셔너리의 항목이 삭제됩니다. 이것은 응용 프로그램의 다른 부분이 소유한 객체에 어트리뷰트를 추가하지 않고도 추가 데이터를 연결하는 데 사용될 수 있습니다. 어트리뷰트 액세스를 재정의하는 객체에 특히 유용할 수 있습니다.

참고

주의: WeakKeyDictionary는 파이썬 딕셔너리 위에 구축되므로, 이터레이션 할 때 크기를 변경해서는 안 됩니다. WeakKeyDictionary 에서 이를 보장하기는 어려울 수 있는데, 이터레이션 중에 프로그램에 의해 수행된 액션으로 인해 딕셔너리의 항목이 "마법처럼" (가비지 수거의 부작용으로) 사라질 수 있기 때문입니다.

버전 3.9에서 변경: PEP 584에 지정된, ||= 연산자에 대한 지원이 추가되었습니다.

WeakKeyDictionary 객체에는 내부 참조를 직접 노출하는 추가 메서드가 있습니다. 참조는 사용되는 시점에 "살아있다고" 보장되지 않아서, 참조를 호출한 결과를 사용하기 전에 확인해야 합니다. 가비지 수거기가 키를 필요 이상으로 길게 유지하도록 하는 참조를 만들지 않도록 하는 데 사용할 수 있습니다.

WeakKeyDictionary.keyrefs()

키에 대한 약한 참조의 이터러블을 반환합니다.

class weakref.WeakValueDictionary([dict])

값을 약하게 참조하는 매핑 클래스. 값에 대한 강한 참조가 더는 존재하지 않을 때 딕셔너리의 항목이 삭제됩니다.

참고

주의: WeakValueDictionary는 파이썬 딕셔너리 위에 구축되므로, 이터레이션 할 때 크기를 변경해서는 안 됩니다. WeakValueDictionary 에서 이를 보장하기는 어려울 수 있는데, 이터레이션 중에 프로그램에 의해 수행된 액션으로 인해 딕셔너리의 항목이 "마법처럼" (가비지 수거의 부작용으로) 사라질 수 있기 때문입니다.

버전 3.9에서 변경: PEP 584에 지정된 대로, ||= 연산자에 대한 지원이 추가되었습니다.

WeakValueDictionary 객체에는 WeakKeyDictionary 객체의 keyrefs() 메서드와 같은 문제가 있는 추가 메서드가 있습니다.

WeakValueDictionary.valuerefs()

값에 대한 약한 참조의 이터러블을 반환합니다.

class weakref.WeakSet([elements])

원소에 대한 약한 참조를 유지하는 집합 클래스. 원소에 대한 강한 참조가 더는 존재하지 않을 때 원소가 삭제됩니다.

class weakref.WeakMethod(method)

연결된 메서드(즉, 클래스에 정의되고 인스턴스에서 조회된 메서드)에 대한 약한 참조를 시뮬레이트 하는 사용자 지정 ref 서브 클래스. 연결된 메서드는 일시적이므로, 표준 약한 참조는 유지할 수 없습니다. WeakMethod에는 객체나 원래 함수가 죽을 때까지 연결된 메서드를 다시 만드는 특별한 코드가 있습니다:

>>> class C:
...     def method(self):
...         print("method called!")
...
>>> c = C()
>>> r = weakref.ref(c.method)
>>> r()
>>> r = weakref.WeakMethod(c.method)
>>> r()
<bound method C.method of <__main__.C object at 0x7fc859830220>>
>>> r()()
method called!
>>> del c
>>> gc.collect()
0
>>> r()
>>>

버전 3.4에 추가.

class weakref.finalize(obj, func, /, *args, **kwargs)

obj가 가비지 수거될 때 호출되는 콜러블 파이널라이저 객체를 반환합니다. 일반적인 약한 참조와 달리, 파이널라이저는 참조 객체가 수집될 때까지 항상 생존하므로, 수명 주기 관리가 크게 간소화됩니다.

파이널라이저는 (명시적으로나 가비지 수거에서) 호출될 때까지 살아있다고 간주하며, 그 후 죽습니다. 살아있는 파이널라이저를 호출하면 func(*arg, **kwargs)를 평가한 결과가 반환되고, 죽은 파이널라이저를 호출하면 None이 반환됩니다.

가비지 수거 중에 파이널라이저 콜백에서 발생한 예외는 표준 에러 출력에 표시되지만, 전파할 수는 없습니다. 객체의 __del__() 메서드나 약한 참조의 콜백에서 발생하는 예외와 같은 방식으로 처리됩니다.

프로그램이 종료될 때, atexit 어트리뷰트가 거짓으로 설정되지 않은 한 각 남은 살아있는 파이널라이저가 호출됩니다. 만들어진 순서와 반대 순서로 호출됩니다.

모듈 전역이 None으로 교체된 경우 인터프리터 종료의 후반 동안에는 파이널라이저가 콜백을 호출하지 않습니다.

__call__()

self가 살아 있으면 이를 죽은 것으로 표시하고 func(*args, **kwargs) 호출 결과를 반환합니다. self가 죽었으면 None을 반환합니다.

detach()

self가 살아 있으면 이를 죽은 것으로 표시하고 튜플 (obj, func, args, kwargs)를 반환합니다. self가 죽었으면 None을 반환합니다.

peek()

self가 살아 있으면 튜플 (obj, func, args, kwargs)를 반환합니다. self가 죽었으면 None을 반환합니다.

alive

파이널라이저가 살아 있으면 참이고, 그렇지 않으면 거짓인 프로퍼티.

atexit

기본값이 참인, 쓰기 가능한 불리언 프로퍼티. 프로그램이 종료할 때, atexit가 참인 남은 모든 살아있는 파이널라이저를 호출합니다. 그것들은 만들어진 순서와 반대 순서로 호출됩니다.

참고

func, argskwargs가 직접이나 간접적으로 obj에 대한 참조를 소유하지 않는 것이 중요합니다. 그렇지 않으면 obj는 가비지 수거되지 않습니다. 특히, funcobj의 연결된 메서드가 아니어야 합니다.

버전 3.4에 추가.

weakref.ReferenceType

약한 참조 객체의 형 객체.

weakref.ProxyType

콜러블이 아닌 객체의 프락시를 위한 형 객체.

weakref.CallableProxyType

콜러블 객체의 프락시를 위한 형 객체.

weakref.ProxyTypes

프락시의 모든 형 객체를 포함하는 시퀀스. 이것은 두 프락시 형 모두의 이름 지정에 의존하지 않고 객체가 프락시인지 검사하기 더 쉽게 만들 수 있습니다.

더 보기

PEP 205 - 약한 참조

이전 구현에 대한 링크와 다른 언어의 유사한 기능에 대한 정보를 포함하는, 이 기능에 대한 제안과 근거.

약한 참조 객체

약한 참조 객체에는 ref.__callback__ 외에 메서드와 어트리뷰트가 없습니다. 약한 참조 객체는 참조대상이 아직 존재한다면 호출함으로써 얻을 수 있도록 합니다:

>>> import weakref
>>> class Object:
...     pass
...
>>> o = Object()
>>> r = weakref.ref(o)
>>> o2 = r()
>>> o is o2
True

참조대상이 더는 존재하지 않을 때, 참조 객체를 호출하면 None을 반환합니다:

>>> del o, o2
>>> print(r())
None

약한 참조 객체가 여전히 살아있는지를 검사하는 것은 ref() is not None 표현식을 사용하여 수행해야 합니다. 일반적으로, 참조 객체를 사용할 필요가 있는 응용 프로그램 코드는 다음 패턴을 따라야 합니다:

# r은 약한 참조 객체입니다
o = r()
if o is None:
    # 참조대상이 가비지 수거되었습니다
    print("Object has been deallocated; can't frobnicate.")
else:
    print("Object is still live!")
    o.do_something_useful()

"생존"에 대해 별도의 검사를 사용하면 스레드 응용 프로그램에서 경쟁 조건이 발생합니다; 약한 참조가 호출되기 전에 다른 스레드가 약한 참조를 무효화 할 수 있습니다; 위에 표시된 관용구는 단일 스레드 응용 프로그램뿐만 아니라 다중 스레드 응용 프로그램에서도 안전합니다.

서브 클래싱을 통해 ref 객체의 특수한 버전을 만들 수 있습니다. 이는 WeakValueDictionary 구현에 사용되어 매핑의 각 항목에 대한 메모리 오버헤드를 줄입니다. 이는 추가 정보를 참조와 연관시키는 데 가장 유용 할 수 있지만, 참조대상을 꺼내기 위한 호출에 추가 처리를 삽입하는 데 사용될 수도 있습니다.

이 예제는 ref의 서브 클래스를 사용하여 객체에 대한 추가 정보를 저장하고 참조대상에 액세스할 때 반환되는 값에 영향을 주는 방법을 보여줍니다:

import weakref

class ExtendedRef(weakref.ref):
    def __init__(self, ob, callback=None, /, **annotations):
        super(ExtendedRef, self).__init__(ob, callback)
        self.__counter = 0
        for k, v in annotations.items():
            setattr(self, k, v)

    def __call__(self):
        """참조대상과 참조가 호출된 횟수를 포함하는 쌍을 반환합니다.
        """
        ob = super(ExtendedRef, self).__call__()
        if ob is not None:
            self.__counter += 1
            ob = (ob, self.__counter)
        return ob

이 간단한 예제는 응용 프로그램이 객체 ID를 사용하여 이전에 본 객체를 조회하는 방법을 보여줍니다. 그런 다음 객체를 강제로 살아있도록 하지 않으면서 다른 자료 구조에서 객체의 ID를 사용할 수 있지만, 살아있다면 객체를 여전히 ID로 조회할 수 있습니다.

import weakref

_id2obj_dict = weakref.WeakValueDictionary()

def remember(obj):
    oid = id(obj)
    _id2obj_dict[oid] = obj
    return oid

def id2obj(oid):
    return _id2obj_dict[oid]

파이널라이저 객체

finalize를 사용해서 얻을 수 있는 주요 이점은 반환된 파이널라이저 객체를 보존할 필요 없이 콜백을 간단하게 등록할 수 있다는 것입니다. 예를 들어

>>> 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!

파이널라이저를 직접 호출할 수도 있습니다. 그러나 파이널라이저는 콜백을 최대 한 번 호출합니다.

>>> def callback(x, y, z):
...     print("CALLBACK")
...     return x + y + z
...
>>> obj = Object()
>>> f = weakref.finalize(obj, callback, 1, 2, z=3)
>>> assert f.alive
>>> assert f() == 6
CALLBACK
>>> assert not f.alive
>>> f()                     # 파이널라이저가 죽었기 때문에 콜백이 호출되지 않습니다
>>> del obj                 # 파이널라이저가 죽었기 때문에 콜백이 호출되지 않습니다

detach() 메서드를 사용하여 파이널라이저를 등록 취소할 수 있습니다. 그러면 파이널라이저를 죽이고 만들어질 때 생성자에 전달된 인자가 반환됩니다.

>>> obj = Object()
>>> f = weakref.finalize(obj, callback, 1, 2, z=3)
>>> f.detach()                                           
(<...Object object ...>, <function callback ...>, (1, 2), {'z': 3})
>>> newobj, func, args, kwargs = _
>>> assert not f.alive
>>> assert newobj is obj
>>> assert func(*args, **kwargs) == 6
CALLBACK

atexit 어트리뷰트를 False로 설정하지 않는 한, 파이널라이저가 살아있다면 프로그램이 종료될 때 호출됩니다. 예를 들어

>>> obj = Object()
>>> weakref.finalize(obj, print, "obj dead or exiting")
<finalize object at ...; for 'Object' at ...>
>>> exit()
obj dead or exiting

파이널라이저와 __del__() 메서드의 비교

인스턴스가 임시 디렉터리를 나타내는 클래스를 만들고 싶다고 가정하십시오. 다음 이벤트 중 첫 번째 것이 발생할 때 디렉터리는 내용과 함께 삭제되어야 합니다:

  • 객체가 가비지 수거됩니다,

  • 객체의 remove() 메서드가 호출됩니다, 또는

  • 프로그램이 종료합니다.

다음과 같이 __del__() 메서드를 사용하여 클래스를 구현하려고 시도할 수 있습니다:

class TempDir:
    def __init__(self):
        self.name = tempfile.mkdtemp()

    def remove(self):
        if self.name is not None:
            shutil.rmtree(self.name)
            self.name = None

    @property
    def removed(self):
        return self.name is None

    def __del__(self):
        self.remove()

파이썬 3.4부터, __del__() 메서드는 더는 참조 순환이 가비지 수거되는 것을 막지 않으며, 인터프리터 종료 중에 모듈 전역이 더는 None으로 강제되지 않습니다. 따라서 이 코드는 CPython에서 아무런 문제 없이 작동해야 합니다.

그러나, __del__() 메서드의 처리는 인터프리터의 가비지 수거기 구현에 대한 내부 세부 사항에 의존하기 때문에 구현에 따라 다르기로 악명 높습니다.

더욱 강인한 대안은 객체의 전체 상태에 액세스하기보다 필요한 특정 함수와 객체만 참조하는 파이널라이저를 정의하는 것일 수 있습니다:

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

이처럼 정의된 파이널라이저는 디렉터리를 적절히 정리하는 데 필요한 세부 사항에 대한 참조만 받습니다. 객체가 가비지 수거되지 않으면 종료 시에 파이널라이저는 여전히 호출됩니다.

약한 참조 기반 파이널라이저의 다른 장점은 제삼자가 정의를 제어하는 클래스에 대해 파이널라이저를 등록하는 데 사용할 수 있다는 것입니다, 가령 모듈이 언로드 될 때 코드 실행하기:

import weakref, sys
def unloading_module():
    # 함수 바디에서 모듈 전역으로의 묵시적 참조
weakref.finalize(sys.modules[__name__], unloading_module)

참고

프로그램이 종료될 때 데몬 스레드에서 파이널라이저 객체를 만들면 종료 시에 파이널라이저가 호출되지 않을 가능성이 있습니다. 그러나, 데몬 스레드 atexit.register()에서, try: ... finally: ...with: ...는 정리가 발생한다고 보장하지 않습니다.