Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/wrapt/weakrefs.py: 17%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""Weak reference proxy for functions and bound methods."""
3import functools
4import weakref
6from .__wrapt__ import BaseObjectProxy, _FunctionWrapperBase
8# A weak function proxy. This will work on instance methods, class
9# methods, static methods and regular functions. Special treatment is
10# needed for the method types because the bound method is effectively a
11# transient object and applying a weak reference to one will immediately
12# result in it being destroyed and the weakref callback called. The weak
13# reference is therefore applied to the instance the method is bound to
14# and the original function. The function is then rebound at the point
15# of a call via the weak function proxy.
16#
17# Where the function is a wrapt function wrapper, such as results from
18# applying a decorator, the same applies but there may be no instance,
19# either because the wrapper was never bound, as for a decorated free
20# function, or because the method was accessed via the class rather
21# than an instance. In that case a weak reference to the class the
22# wrapper was accessed through, the owner, is retained as well so that
23# the function can still be rebound correctly at the point of a call.
26def _weak_function_proxy_callback(ref, proxy, callback):
27 if proxy._self_expired:
28 return
30 proxy._self_expired = True
32 # This could raise an exception. We let it propagate back and let
33 # the weakref.proxy() deal with it, at which point it generally
34 # prints out a short error message direct to stderr and keeps going.
36 if callback is not None:
37 callback(proxy)
40class WeakFunctionProxy(BaseObjectProxy):
41 """A weak function proxy."""
43 def __init__(self, wrapped, callback=None):
44 """Create a proxy to object which uses a weak reference. This is
45 similar to the `weakref.proxy` but is designed to work with functions
46 and methods. It will automatically rebind the function to the instance
47 when called if the function was originally a bound method. This is
48 necessary because bound methods are transient objects and applying a
49 weak reference to one will immediately result in it being destroyed
50 and the weakref callback called. The weak reference is therefore
51 applied to the instance the method is bound to and the original
52 function. The function is then rebound at the point of a call via the
53 weak function proxy.
54 """
56 # We need to determine if the wrapped function is actually a
57 # bound method. In the case of a bound method, we need to keep a
58 # reference to the original unbound function and the instance.
59 # This is necessary because if we hold a reference to the bound
60 # function, it will be the only reference and given it is a
61 # temporary object, it will almost immediately expire and
62 # the weakref callback triggered. So what is done is that we
63 # hold a reference to the instance and unbound function and
64 # when called bind the function to the instance once again and
65 # then call it. Note that we avoid using a nested function for
66 # the callback here so as not to cause any odd reference cycles.
68 _callback = callback and functools.partial(
69 _weak_function_proxy_callback, proxy=self, callback=callback
70 )
72 self._self_expired = False
73 self._self_owner = None
75 if isinstance(wrapped, _FunctionWrapperBase):
76 # A function wrapper may have no instance, either because it
77 # was never bound, as for a decorated free function, or
78 # because the method was accessed via the class rather than
79 # an instance. Only take a weak reference to the instance
80 # where there is one. The owner is the class the wrapper was
81 # accessed through, and is retained so the function can be
82 # rebound with it when called. Without it a classmethod
83 # accessed via the class could not be rebound at all, and an
84 # instance method accessed via the class would not be able
85 # to identify an instance passed as the first argument.
87 instance = wrapped._self_instance
88 self._self_instance = (
89 weakref.ref(instance, _callback) if instance is not None else None
90 )
91 owner = wrapped._self_owner
92 if owner is not None:
93 self._self_owner = weakref.ref(owner, _callback)
95 if wrapped._self_parent is not None:
96 # Explicit class in super() is used because the proxy
97 # overrides __class__ and MRO-related methods to delegate
98 # to the wrapped object, which can interfere with bare
99 # super().
100 super(WeakFunctionProxy, self).__init__(
101 weakref.proxy(wrapped._self_parent, _callback)
102 )
104 else:
105 super(WeakFunctionProxy, self).__init__(
106 weakref.proxy(wrapped, _callback)
107 )
109 return
111 try:
112 self._self_instance = weakref.ref(wrapped.__self__, _callback)
114 super(WeakFunctionProxy, self).__init__(
115 weakref.proxy(wrapped.__func__, _callback)
116 )
118 except AttributeError:
119 self._self_instance = None
121 super(WeakFunctionProxy, self).__init__(weakref.proxy(wrapped, _callback))
123 def __call__(*args, **kwargs):
124 def _unpack_self(self, *args):
125 return self, args
127 self, args = _unpack_self(*args)
129 # We perform a boolean check here on the instance and wrapped
130 # function as that will trigger the reference error prior to
131 # calling if the reference had expired.
133 instance = self._self_instance and self._self_instance()
134 function = self.__wrapped__ and self.__wrapped__
136 # If the wrapped function was originally a bound method but the
137 # instance it was bound to has been garbage collected, raise a
138 # ReferenceError rather than silently calling it as unbound.
140 if self._self_instance is not None and instance is None:
141 raise ReferenceError("weakly-referenced object no longer exists")
143 # If the wrapped function was a function wrapper for which the
144 # owner was retained, rebind the function against the instance,
145 # which may be None, and that owner. This is what a classmethod
146 # accessed via the class needs to be rebound at all, and what an
147 # instance method accessed via the class needs to recognise an
148 # instance passed as the first argument. If the owner has been
149 # garbage collected, raise a ReferenceError as for the instance.
151 if self._self_owner is not None:
152 owner = self._self_owner()
153 if owner is None:
154 raise ReferenceError("weakly-referenced object no longer exists")
155 return function.__get__(instance, owner)(*args, **kwargs)
157 # Otherwise, if the wrapped function was originally a bound
158 # function, for which we retained a reference to the instance and
159 # the unbound function, we need to rebind the function and then
160 # call it. If not just call the wrapped function.
162 if instance is None:
163 return self.__wrapped__(*args, **kwargs)
165 return function.__get__(instance, type(instance))(*args, **kwargs)