1 """Weak reference support for Python.
2
3 This module is an implementation of PEP 205:
4
5 https://peps.python.org/pep-0205/
6 """
7
8 # Naming convention: Variables named "wr" are weak reference objects;
9 # they are called this instead of "ref" to avoid name collisions with
10 # the module-global ref() function imported from _weakref.
11
12 from _weakref import (
13 getweakrefcount,
14 getweakrefs,
15 ref,
16 proxy,
17 CallableProxyType,
18 ProxyType,
19 ReferenceType,
20 _remove_dead_weakref)
21
22 from _weakrefset import WeakSet, _IterationGuard
23
24 import _collections_abc # Import after _weakref to avoid circular import.
25 import sys
26 import itertools
27
28 ProxyTypes = (ProxyType, CallableProxyType)
29
30 __all__ = ["ref", "proxy", "getweakrefcount", "getweakrefs",
31 "WeakKeyDictionary", "ReferenceType", "ProxyType",
32 "CallableProxyType", "ProxyTypes", "WeakValueDictionary",
33 "WeakSet", "WeakMethod", "finalize"]
34
35
36 _collections_abc.MutableSet.register(WeakSet)
37
38 class ESC[4;38;5;81mWeakMethod(ESC[4;38;5;149mref):
39 """
40 A custom `weakref.ref` subclass which simulates a weak reference to
41 a bound method, working around the lifetime problem of bound methods.
42 """
43
44 __slots__ = "_func_ref", "_meth_type", "_alive", "__weakref__"
45
46 def __new__(cls, meth, callback=None):
47 try:
48 obj = meth.__self__
49 func = meth.__func__
50 except AttributeError:
51 raise TypeError("argument should be a bound method, not {}"
52 .format(type(meth))) from None
53 def _cb(arg):
54 # The self-weakref trick is needed to avoid creating a reference
55 # cycle.
56 self = self_wr()
57 if self._alive:
58 self._alive = False
59 if callback is not None:
60 callback(self)
61 self = ref.__new__(cls, obj, _cb)
62 self._func_ref = ref(func, _cb)
63 self._meth_type = type(meth)
64 self._alive = True
65 self_wr = ref(self)
66 return self
67
68 def __call__(self):
69 obj = super().__call__()
70 func = self._func_ref()
71 if obj is None or func is None:
72 return None
73 return self._meth_type(func, obj)
74
75 def __eq__(self, other):
76 if isinstance(other, WeakMethod):
77 if not self._alive or not other._alive:
78 return self is other
79 return ref.__eq__(self, other) and self._func_ref == other._func_ref
80 return NotImplemented
81
82 def __ne__(self, other):
83 if isinstance(other, WeakMethod):
84 if not self._alive or not other._alive:
85 return self is not other
86 return ref.__ne__(self, other) or self._func_ref != other._func_ref
87 return NotImplemented
88
89 __hash__ = ref.__hash__
90
91
92 class ESC[4;38;5;81mWeakValueDictionary(ESC[4;38;5;149m_collections_abcESC[4;38;5;149m.ESC[4;38;5;149mMutableMapping):
93 """Mapping class that references values weakly.
94
95 Entries in the dictionary will be discarded when no strong
96 reference to the value exists anymore
97 """
98 # We inherit the constructor without worrying about the input
99 # dictionary; since it uses our .update() method, we get the right
100 # checks (if the other dictionary is a WeakValueDictionary,
101 # objects are unwrapped on the way out, and we always wrap on the
102 # way in).
103
104 def __init__(self, other=(), /, **kw):
105 def remove(wr, selfref=ref(self), _atomic_removal=_remove_dead_weakref):
106 self = selfref()
107 if self is not None:
108 if self._iterating:
109 self._pending_removals.append(wr.key)
110 else:
111 # Atomic removal is necessary since this function
112 # can be called asynchronously by the GC
113 _atomic_removal(self.data, wr.key)
114 self._remove = remove
115 # A list of keys to be removed
116 self._pending_removals = []
117 self._iterating = set()
118 self.data = {}
119 self.update(other, **kw)
120
121 def _commit_removals(self, _atomic_removal=_remove_dead_weakref):
122 pop = self._pending_removals.pop
123 d = self.data
124 # We shouldn't encounter any KeyError, because this method should
125 # always be called *before* mutating the dict.
126 while True:
127 try:
128 key = pop()
129 except IndexError:
130 return
131 _atomic_removal(d, key)
132
133 def __getitem__(self, key):
134 if self._pending_removals:
135 self._commit_removals()
136 o = self.data[key]()
137 if o is None:
138 raise KeyError(key)
139 else:
140 return o
141
142 def __delitem__(self, key):
143 if self._pending_removals:
144 self._commit_removals()
145 del self.data[key]
146
147 def __len__(self):
148 if self._pending_removals:
149 self._commit_removals()
150 return len(self.data)
151
152 def __contains__(self, key):
153 if self._pending_removals:
154 self._commit_removals()
155 try:
156 o = self.data[key]()
157 except KeyError:
158 return False
159 return o is not None
160
161 def __repr__(self):
162 return "<%s at %#x>" % (self.__class__.__name__, id(self))
163
164 def __setitem__(self, key, value):
165 if self._pending_removals:
166 self._commit_removals()
167 self.data[key] = KeyedRef(value, self._remove, key)
168
169 def copy(self):
170 if self._pending_removals:
171 self._commit_removals()
172 new = WeakValueDictionary()
173 with _IterationGuard(self):
174 for key, wr in self.data.items():
175 o = wr()
176 if o is not None:
177 new[key] = o
178 return new
179
180 __copy__ = copy
181
182 def __deepcopy__(self, memo):
183 from copy import deepcopy
184 if self._pending_removals:
185 self._commit_removals()
186 new = self.__class__()
187 with _IterationGuard(self):
188 for key, wr in self.data.items():
189 o = wr()
190 if o is not None:
191 new[deepcopy(key, memo)] = o
192 return new
193
194 def get(self, key, default=None):
195 if self._pending_removals:
196 self._commit_removals()
197 try:
198 wr = self.data[key]
199 except KeyError:
200 return default
201 else:
202 o = wr()
203 if o is None:
204 # This should only happen
205 return default
206 else:
207 return o
208
209 def items(self):
210 if self._pending_removals:
211 self._commit_removals()
212 with _IterationGuard(self):
213 for k, wr in self.data.items():
214 v = wr()
215 if v is not None:
216 yield k, v
217
218 def keys(self):
219 if self._pending_removals:
220 self._commit_removals()
221 with _IterationGuard(self):
222 for k, wr in self.data.items():
223 if wr() is not None:
224 yield k
225
226 __iter__ = keys
227
228 def itervaluerefs(self):
229 """Return an iterator that yields the weak references to the values.
230
231 The references are not guaranteed to be 'live' at the time
232 they are used, so the result of calling the references needs
233 to be checked before being used. This can be used to avoid
234 creating references that will cause the garbage collector to
235 keep the values around longer than needed.
236
237 """
238 if self._pending_removals:
239 self._commit_removals()
240 with _IterationGuard(self):
241 yield from self.data.values()
242
243 def values(self):
244 if self._pending_removals:
245 self._commit_removals()
246 with _IterationGuard(self):
247 for wr in self.data.values():
248 obj = wr()
249 if obj is not None:
250 yield obj
251
252 def popitem(self):
253 if self._pending_removals:
254 self._commit_removals()
255 while True:
256 key, wr = self.data.popitem()
257 o = wr()
258 if o is not None:
259 return key, o
260
261 def pop(self, key, *args):
262 if self._pending_removals:
263 self._commit_removals()
264 try:
265 o = self.data.pop(key)()
266 except KeyError:
267 o = None
268 if o is None:
269 if args:
270 return args[0]
271 else:
272 raise KeyError(key)
273 else:
274 return o
275
276 def setdefault(self, key, default=None):
277 try:
278 o = self.data[key]()
279 except KeyError:
280 o = None
281 if o is None:
282 if self._pending_removals:
283 self._commit_removals()
284 self.data[key] = KeyedRef(default, self._remove, key)
285 return default
286 else:
287 return o
288
289 def update(self, other=None, /, **kwargs):
290 if self._pending_removals:
291 self._commit_removals()
292 d = self.data
293 if other is not None:
294 if not hasattr(other, "items"):
295 other = dict(other)
296 for key, o in other.items():
297 d[key] = KeyedRef(o, self._remove, key)
298 for key, o in kwargs.items():
299 d[key] = KeyedRef(o, self._remove, key)
300
301 def valuerefs(self):
302 """Return a list of weak references to the values.
303
304 The references are not guaranteed to be 'live' at the time
305 they are used, so the result of calling the references needs
306 to be checked before being used. This can be used to avoid
307 creating references that will cause the garbage collector to
308 keep the values around longer than needed.
309
310 """
311 if self._pending_removals:
312 self._commit_removals()
313 return list(self.data.values())
314
315 def __ior__(self, other):
316 self.update(other)
317 return self
318
319 def __or__(self, other):
320 if isinstance(other, _collections_abc.Mapping):
321 c = self.copy()
322 c.update(other)
323 return c
324 return NotImplemented
325
326 def __ror__(self, other):
327 if isinstance(other, _collections_abc.Mapping):
328 c = self.__class__()
329 c.update(other)
330 c.update(self)
331 return c
332 return NotImplemented
333
334
335 class ESC[4;38;5;81mKeyedRef(ESC[4;38;5;149mref):
336 """Specialized reference that includes a key corresponding to the value.
337
338 This is used in the WeakValueDictionary to avoid having to create
339 a function object for each key stored in the mapping. A shared
340 callback object can use the 'key' attribute of a KeyedRef instead
341 of getting a reference to the key from an enclosing scope.
342
343 """
344
345 __slots__ = "key",
346
347 def __new__(type, ob, callback, key):
348 self = ref.__new__(type, ob, callback)
349 self.key = key
350 return self
351
352 def __init__(self, ob, callback, key):
353 super().__init__(ob, callback)
354
355
356 class ESC[4;38;5;81mWeakKeyDictionary(ESC[4;38;5;149m_collections_abcESC[4;38;5;149m.ESC[4;38;5;149mMutableMapping):
357 """ Mapping class that references keys weakly.
358
359 Entries in the dictionary will be discarded when there is no
360 longer a strong reference to the key. This can be used to
361 associate additional data with an object owned by other parts of
362 an application without adding attributes to those objects. This
363 can be especially useful with objects that override attribute
364 accesses.
365 """
366
367 def __init__(self, dict=None):
368 self.data = {}
369 def remove(k, selfref=ref(self)):
370 self = selfref()
371 if self is not None:
372 if self._iterating:
373 self._pending_removals.append(k)
374 else:
375 try:
376 del self.data[k]
377 except KeyError:
378 pass
379 self._remove = remove
380 # A list of dead weakrefs (keys to be removed)
381 self._pending_removals = []
382 self._iterating = set()
383 self._dirty_len = False
384 if dict is not None:
385 self.update(dict)
386
387 def _commit_removals(self):
388 # NOTE: We don't need to call this method before mutating the dict,
389 # because a dead weakref never compares equal to a live weakref,
390 # even if they happened to refer to equal objects.
391 # However, it means keys may already have been removed.
392 pop = self._pending_removals.pop
393 d = self.data
394 while True:
395 try:
396 key = pop()
397 except IndexError:
398 return
399
400 try:
401 del d[key]
402 except KeyError:
403 pass
404
405 def _scrub_removals(self):
406 d = self.data
407 self._pending_removals = [k for k in self._pending_removals if k in d]
408 self._dirty_len = False
409
410 def __delitem__(self, key):
411 self._dirty_len = True
412 del self.data[ref(key)]
413
414 def __getitem__(self, key):
415 return self.data[ref(key)]
416
417 def __len__(self):
418 if self._dirty_len and self._pending_removals:
419 # self._pending_removals may still contain keys which were
420 # explicitly removed, we have to scrub them (see issue #21173).
421 self._scrub_removals()
422 return len(self.data) - len(self._pending_removals)
423
424 def __repr__(self):
425 return "<%s at %#x>" % (self.__class__.__name__, id(self))
426
427 def __setitem__(self, key, value):
428 self.data[ref(key, self._remove)] = value
429
430 def copy(self):
431 new = WeakKeyDictionary()
432 with _IterationGuard(self):
433 for key, value in self.data.items():
434 o = key()
435 if o is not None:
436 new[o] = value
437 return new
438
439 __copy__ = copy
440
441 def __deepcopy__(self, memo):
442 from copy import deepcopy
443 new = self.__class__()
444 with _IterationGuard(self):
445 for key, value in self.data.items():
446 o = key()
447 if o is not None:
448 new[o] = deepcopy(value, memo)
449 return new
450
451 def get(self, key, default=None):
452 return self.data.get(ref(key),default)
453
454 def __contains__(self, key):
455 try:
456 wr = ref(key)
457 except TypeError:
458 return False
459 return wr in self.data
460
461 def items(self):
462 with _IterationGuard(self):
463 for wr, value in self.data.items():
464 key = wr()
465 if key is not None:
466 yield key, value
467
468 def keys(self):
469 with _IterationGuard(self):
470 for wr in self.data:
471 obj = wr()
472 if obj is not None:
473 yield obj
474
475 __iter__ = keys
476
477 def values(self):
478 with _IterationGuard(self):
479 for wr, value in self.data.items():
480 if wr() is not None:
481 yield value
482
483 def keyrefs(self):
484 """Return a list of weak references to the keys.
485
486 The references are not guaranteed to be 'live' at the time
487 they are used, so the result of calling the references needs
488 to be checked before being used. This can be used to avoid
489 creating references that will cause the garbage collector to
490 keep the keys around longer than needed.
491
492 """
493 return list(self.data)
494
495 def popitem(self):
496 self._dirty_len = True
497 while True:
498 key, value = self.data.popitem()
499 o = key()
500 if o is not None:
501 return o, value
502
503 def pop(self, key, *args):
504 self._dirty_len = True
505 return self.data.pop(ref(key), *args)
506
507 def setdefault(self, key, default=None):
508 return self.data.setdefault(ref(key, self._remove),default)
509
510 def update(self, dict=None, /, **kwargs):
511 d = self.data
512 if dict is not None:
513 if not hasattr(dict, "items"):
514 dict = type({})(dict)
515 for key, value in dict.items():
516 d[ref(key, self._remove)] = value
517 if len(kwargs):
518 self.update(kwargs)
519
520 def __ior__(self, other):
521 self.update(other)
522 return self
523
524 def __or__(self, other):
525 if isinstance(other, _collections_abc.Mapping):
526 c = self.copy()
527 c.update(other)
528 return c
529 return NotImplemented
530
531 def __ror__(self, other):
532 if isinstance(other, _collections_abc.Mapping):
533 c = self.__class__()
534 c.update(other)
535 c.update(self)
536 return c
537 return NotImplemented
538
539
540 class ESC[4;38;5;81mfinalize:
541 """Class for finalization of weakrefable objects
542
543 finalize(obj, func, *args, **kwargs) returns a callable finalizer
544 object which will be called when obj is garbage collected. The
545 first time the finalizer is called it evaluates func(*arg, **kwargs)
546 and returns the result. After this the finalizer is dead, and
547 calling it just returns None.
548
549 When the program exits any remaining finalizers for which the
550 atexit attribute is true will be run in reverse order of creation.
551 By default atexit is true.
552 """
553
554 # Finalizer objects don't have any state of their own. They are
555 # just used as keys to lookup _Info objects in the registry. This
556 # ensures that they cannot be part of a ref-cycle.
557
558 __slots__ = ()
559 _registry = {}
560 _shutdown = False
561 _index_iter = itertools.count()
562 _dirty = False
563 _registered_with_atexit = False
564
565 class ESC[4;38;5;81m_Info:
566 __slots__ = ("weakref", "func", "args", "kwargs", "atexit", "index")
567
568 def __init__(self, obj, func, /, *args, **kwargs):
569 if not self._registered_with_atexit:
570 # We may register the exit function more than once because
571 # of a thread race, but that is harmless
572 import atexit
573 atexit.register(self._exitfunc)
574 finalize._registered_with_atexit = True
575 info = self._Info()
576 info.weakref = ref(obj, self)
577 info.func = func
578 info.args = args
579 info.kwargs = kwargs or None
580 info.atexit = True
581 info.index = next(self._index_iter)
582 self._registry[self] = info
583 finalize._dirty = True
584
585 def __call__(self, _=None):
586 """If alive then mark as dead and return func(*args, **kwargs);
587 otherwise return None"""
588 info = self._registry.pop(self, None)
589 if info and not self._shutdown:
590 return info.func(*info.args, **(info.kwargs or {}))
591
592 def detach(self):
593 """If alive then mark as dead and return (obj, func, args, kwargs);
594 otherwise return None"""
595 info = self._registry.get(self)
596 obj = info and info.weakref()
597 if obj is not None and self._registry.pop(self, None):
598 return (obj, info.func, info.args, info.kwargs or {})
599
600 def peek(self):
601 """If alive then return (obj, func, args, kwargs);
602 otherwise return None"""
603 info = self._registry.get(self)
604 obj = info and info.weakref()
605 if obj is not None:
606 return (obj, info.func, info.args, info.kwargs or {})
607
608 @property
609 def alive(self):
610 """Whether finalizer is alive"""
611 return self in self._registry
612
613 @property
614 def atexit(self):
615 """Whether finalizer should be called at exit"""
616 info = self._registry.get(self)
617 return bool(info) and info.atexit
618
619 @atexit.setter
620 def atexit(self, value):
621 info = self._registry.get(self)
622 if info:
623 info.atexit = bool(value)
624
625 def __repr__(self):
626 info = self._registry.get(self)
627 obj = info and info.weakref()
628 if obj is None:
629 return '<%s object at %#x; dead>' % (type(self).__name__, id(self))
630 else:
631 return '<%s object at %#x; for %r at %#x>' % \
632 (type(self).__name__, id(self), type(obj).__name__, id(obj))
633
634 @classmethod
635 def _select_for_exit(cls):
636 # Return live finalizers marked for exit, oldest first
637 L = [(f,i) for (f,i) in cls._registry.items() if i.atexit]
638 L.sort(key=lambda item:item[1].index)
639 return [f for (f,i) in L]
640
641 @classmethod
642 def _exitfunc(cls):
643 # At shutdown invoke finalizers for which atexit is true.
644 # This is called once all other non-daemonic threads have been
645 # joined.
646 reenable_gc = False
647 try:
648 if cls._registry:
649 import gc
650 if gc.isenabled():
651 reenable_gc = True
652 gc.disable()
653 pending = None
654 while True:
655 if pending is None or finalize._dirty:
656 pending = cls._select_for_exit()
657 finalize._dirty = False
658 if not pending:
659 break
660 f = pending.pop()
661 try:
662 # gc is disabled, so (assuming no daemonic
663 # threads) the following is the only line in
664 # this function which might trigger creation
665 # of a new finalizer
666 f()
667 except Exception:
668 sys.excepthook(*sys.exc_info())
669 assert f not in cls._registry
670 finally:
671 # prevent any more finalizers from executing during shutdown
672 finalize._shutdown = True
673 if reenable_gc:
674 gc.enable()