Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/wrapt/weakrefs.py: 16%
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.
35 # Note that the callback can never actually be None here, as the
36 # partial which invokes this function is only created when a
37 # callback was supplied, but check anyway as a safeguard.
39 if callback is not None:
40 callback(proxy)
43class WeakFunctionProxy(BaseObjectProxy):
44 """A weak function proxy."""
46 def __init__(self, wrapped, callback=None):
47 """Create a proxy to object which uses a weak reference. This is
48 similar to the `weakref.proxy` but is designed to work with functions
49 and methods. It will automatically rebind the function to the instance
50 when called if the function was originally a bound method. This is
51 necessary because bound methods are transient objects and applying a
52 weak reference to one will immediately result in it being destroyed
53 and the weakref callback called. The weak reference is therefore
54 applied to the instance the method is bound to and the original
55 function. The function is then rebound at the point of a call via the
56 weak function proxy.
57 """
59 # We need to determine if the wrapped function is actually a
60 # bound method. In the case of a bound method, we need to keep a
61 # reference to the original unbound function and the instance.
62 # This is necessary because if we hold a reference to the bound
63 # function, it will be the only reference and given it is a
64 # temporary object, it will almost immediately expire and
65 # the weakref callback triggered. So what is done is that we
66 # hold a reference to the instance and unbound function and
67 # when called bind the function to the instance once again and
68 # then call it. Note that we avoid using a nested function for
69 # the callback here so as not to cause any odd reference cycles.
71 _callback = None
72 if callback is not None:
73 _callback = functools.partial(
74 _weak_function_proxy_callback, proxy=self, callback=callback
75 )
77 self._self_expired = False
78 self._self_owner = None
80 if isinstance(wrapped, _FunctionWrapperBase):
81 # A function wrapper may have no instance, either because it
82 # was never bound, as for a decorated free function, or
83 # because the method was accessed via the class rather than
84 # an instance. Only take a weak reference to the instance
85 # where there is one. The owner is the class the wrapper was
86 # accessed through, and is retained so the function can be
87 # rebound with it when called. Without it a classmethod
88 # accessed via the class could not be rebound at all, and an
89 # instance method accessed via the class would not be able
90 # to identify an instance passed as the first argument.
92 instance = wrapped._self_instance
93 self._self_instance = (
94 weakref.ref(instance, _callback) if instance is not None else None
95 )
96 owner = wrapped._self_owner
97 if owner is not None:
98 self._self_owner = weakref.ref(owner, _callback)
100 if wrapped._self_parent is not None:
101 # Explicit class in super() is used because the proxy
102 # overrides __class__ and MRO-related methods to delegate
103 # to the wrapped object, which can interfere with bare
104 # super().
105 super(WeakFunctionProxy, self).__init__(
106 weakref.proxy(wrapped._self_parent, _callback)
107 )
109 else:
110 super(WeakFunctionProxy, self).__init__(
111 weakref.proxy(wrapped, _callback)
112 )
114 return
116 try:
117 self._self_instance = weakref.ref(wrapped.__self__, _callback)
119 super(WeakFunctionProxy, self).__init__(
120 weakref.proxy(wrapped.__func__, _callback)
121 )
123 except AttributeError:
124 self._self_instance = None
126 super(WeakFunctionProxy, self).__init__(weakref.proxy(wrapped, _callback))
128 def __call__(*args, **kwargs):
129 def _unpack_self(self, *args):
130 return self, args
132 self, args = _unpack_self(*args)
134 # We perform a boolean check here on the instance and wrapped
135 # function as that will trigger the reference error prior to
136 # calling if the reference had expired.
138 instance = self._self_instance and self._self_instance()
139 function = self.__wrapped__ and self.__wrapped__
141 # If the wrapped function was originally a bound method but the
142 # instance it was bound to has been garbage collected, raise a
143 # ReferenceError rather than silently calling it as unbound.
145 if self._self_instance is not None and instance is None:
146 raise ReferenceError("weakly-referenced object no longer exists")
148 # If the wrapped function was a function wrapper for which the
149 # owner was retained, rebind the function against the instance,
150 # which may be None, and that owner. This is what a classmethod
151 # accessed via the class needs to be rebound at all, and what an
152 # instance method accessed via the class needs to recognise an
153 # instance passed as the first argument. If the owner has been
154 # garbage collected, raise a ReferenceError as for the instance.
156 if self._self_owner is not None:
157 owner = self._self_owner()
158 if owner is None:
159 raise ReferenceError("weakly-referenced object no longer exists")
160 return function.__get__(instance, owner)(*args, **kwargs)
162 # Otherwise, if the wrapped function was originally a bound
163 # function, for which we retained a reference to the instance and
164 # the unbound function, we need to rebind the function and then
165 # call it. If not just call the wrapped function.
167 if instance is None:
168 return self.__wrapped__(*args, **kwargs)
170 return function.__get__(instance, type(instance))(*args, **kwargs)