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

49 statements  

1"""Weak reference proxy for functions and bound methods.""" 

2 

3import functools 

4import weakref 

5 

6from .__wrapt__ import BaseObjectProxy, _FunctionWrapperBase 

7 

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. 

24 

25 

26def _weak_function_proxy_callback(ref, proxy, callback): 

27 if proxy._self_expired: 

28 return 

29 

30 proxy._self_expired = True 

31 

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. 

38 

39 if callback is not None: 

40 callback(proxy) 

41 

42 

43class WeakFunctionProxy(BaseObjectProxy): 

44 """A weak function proxy.""" 

45 

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

58 

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. 

70 

71 _callback = None 

72 if callback is not None: 

73 _callback = functools.partial( 

74 _weak_function_proxy_callback, proxy=self, callback=callback 

75 ) 

76 

77 self._self_expired = False 

78 self._self_owner = None 

79 

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. 

91 

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) 

99 

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 ) 

108 

109 else: 

110 super(WeakFunctionProxy, self).__init__( 

111 weakref.proxy(wrapped, _callback) 

112 ) 

113 

114 return 

115 

116 try: 

117 self._self_instance = weakref.ref(wrapped.__self__, _callback) 

118 

119 super(WeakFunctionProxy, self).__init__( 

120 weakref.proxy(wrapped.__func__, _callback) 

121 ) 

122 

123 except AttributeError: 

124 self._self_instance = None 

125 

126 super(WeakFunctionProxy, self).__init__(weakref.proxy(wrapped, _callback)) 

127 

128 def __call__(*args, **kwargs): 

129 def _unpack_self(self, *args): 

130 return self, args 

131 

132 self, args = _unpack_self(*args) 

133 

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. 

137 

138 instance = self._self_instance and self._self_instance() 

139 function = self.__wrapped__ and self.__wrapped__ 

140 

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. 

144 

145 if self._self_instance is not None and instance is None: 

146 raise ReferenceError("weakly-referenced object no longer exists") 

147 

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. 

155 

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) 

161 

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. 

166 

167 if instance is None: 

168 return self.__wrapped__(*args, **kwargs) 

169 

170 return function.__get__(instance, type(instance))(*args, **kwargs)