a2f2966e5496601787d138e9004fbb3d2ce9b64c
[subu] /
1 # -*- coding: utf-8 -*-
2 """
3 backports.weakref_finalize
4 ~~~~~~~~~~~~~~~~~~
5
6 Backports the Python 3 ``weakref.finalize`` method.
7 """
8 from __future__ import absolute_import
9
10 import itertools
11 import sys
12 from weakref import ref
13
14 __all__ = ["weakref_finalize"]
15
16
17 class weakref_finalize(object):
18     """Class for finalization of weakrefable objects
19     finalize(obj, func, *args, **kwargs) returns a callable finalizer
20     object which will be called when obj is garbage collected. The
21     first time the finalizer is called it evaluates func(*arg, **kwargs)
22     and returns the result. After this the finalizer is dead, and
23     calling it just returns None.
24     When the program exits any remaining finalizers for which the
25     atexit attribute is true will be run in reverse order of creation.
26     By default atexit is true.
27     """
28
29     # Finalizer objects don't have any state of their own.  They are
30     # just used as keys to lookup _Info objects in the registry.  This
31     # ensures that they cannot be part of a ref-cycle.
32
33     __slots__ = ()
34     _registry = {}
35     _shutdown = False
36     _index_iter = itertools.count()
37     _dirty = False
38     _registered_with_atexit = False
39
40     class _Info(object):
41         __slots__ = ("weakref", "func", "args", "kwargs", "atexit", "index")
42
43     def __init__(self, obj, func, *args, **kwargs):
44         if not self._registered_with_atexit:
45             # We may register the exit function more than once because
46             # of a thread race, but that is harmless
47             import atexit
48
49             atexit.register(self._exitfunc)
50             weakref_finalize._registered_with_atexit = True
51         info = self._Info()
52         info.weakref = ref(obj, self)
53         info.func = func
54         info.args = args
55         info.kwargs = kwargs or None
56         info.atexit = True
57         info.index = next(self._index_iter)
58         self._registry[self] = info
59         weakref_finalize._dirty = True
60
61     def __call__(self, _=None):
62         """If alive then mark as dead and return func(*args, **kwargs);
63         otherwise return None"""
64         info = self._registry.pop(self, None)
65         if info and not self._shutdown:
66             return info.func(*info.args, **(info.kwargs or {}))
67
68     def detach(self):
69         """If alive then mark as dead and return (obj, func, args, kwargs);
70         otherwise return None"""
71         info = self._registry.get(self)
72         obj = info and info.weakref()
73         if obj is not None and self._registry.pop(self, None):
74             return (obj, info.func, info.args, info.kwargs or {})
75
76     def peek(self):
77         """If alive then return (obj, func, args, kwargs);
78         otherwise return None"""
79         info = self._registry.get(self)
80         obj = info and info.weakref()
81         if obj is not None:
82             return (obj, info.func, info.args, info.kwargs or {})
83
84     @property
85     def alive(self):
86         """Whether finalizer is alive"""
87         return self in self._registry
88
89     @property
90     def atexit(self):
91         """Whether finalizer should be called at exit"""
92         info = self._registry.get(self)
93         return bool(info) and info.atexit
94
95     @atexit.setter
96     def atexit(self, value):
97         info = self._registry.get(self)
98         if info:
99             info.atexit = bool(value)
100
101     def __repr__(self):
102         info = self._registry.get(self)
103         obj = info and info.weakref()
104         if obj is None:
105             return "<%s object at %#x; dead>" % (type(self).__name__, id(self))
106         else:
107             return "<%s object at %#x; for %r at %#x>" % (
108                 type(self).__name__,
109                 id(self),
110                 type(obj).__name__,
111                 id(obj),
112             )
113
114     @classmethod
115     def _select_for_exit(cls):
116         # Return live finalizers marked for exit, oldest first
117         L = [(f, i) for (f, i) in cls._registry.items() if i.atexit]
118         L.sort(key=lambda item: item[1].index)
119         return [f for (f, i) in L]
120
121     @classmethod
122     def _exitfunc(cls):
123         # At shutdown invoke finalizers for which atexit is true.
124         # This is called once all other non-daemonic threads have been
125         # joined.
126         reenable_gc = False
127         try:
128             if cls._registry:
129                 import gc
130
131                 if gc.isenabled():
132                     reenable_gc = True
133                     gc.disable()
134                 pending = None
135                 while True:
136                     if pending is None or weakref_finalize._dirty:
137                         pending = cls._select_for_exit()
138                         weakref_finalize._dirty = False
139                     if not pending:
140                         break
141                     f = pending.pop()
142                     try:
143                         # gc is disabled, so (assuming no daemonic
144                         # threads) the following is the only line in
145                         # this function which might trigger creation
146                         # of a new finalizer
147                         f()
148                     except Exception:
149                         sys.excepthook(*sys.exc_info())
150                     assert f not in cls._registry
151         finally:
152             # prevent any more finalizers from executing during shutdown
153             weakref_finalize._shutdown = True
154             if reenable_gc:
155                 gc.enable()