14
14
import glob
15
15
import gzip
16
16
import io
17
- from itertools import repeat
17
+ import inspect
18
+ import itertools
18
19
import locale
19
20
import numbers
20
21
import operator
26
27
import traceback
27
28
import types
28
29
import warnings
29
- from weakref import ref , WeakKeyDictionary
30
+ import weakref
31
+ from weakref import WeakMethod
30
32
31
33
import numpy as np
32
34
@@ -61,100 +63,26 @@ def unicode_safe(s):
61
63
return s
62
64
63
65
64
- class _BoundMethodProxy (object ):
65
- """
66
- Our own proxy object which enables weak references to bound and unbound
67
- methods and arbitrary callables. Pulls information about the function,
68
- class, and instance out of a bound method. Stores a weak reference to the
69
- instance to support garbage collection.
66
+ def _exception_printer (exc ):
67
+ traceback .print_exc ()
70
68
71
- @organization: IBM Corporation
72
- @copyright: Copyright (c) 2005, 2006 IBM Corporation
73
- @license: The BSD License
74
69
75
- Minor bugfixes by Michael Droettboom
70
+ class _StrongRef :
71
+ """
72
+ Wrapper similar to a weakref, but keeping a strong reference to the object.
76
73
"""
77
- def __init__ (self , cb ):
78
- self ._hash = hash (cb )
79
- self ._destroy_callbacks = []
80
- try :
81
- try :
82
- self .inst = ref (cb .__self__ , self ._destroy )
83
- except TypeError :
84
- self .inst = None
85
- self .func = cb .__func__
86
- self .klass = cb .__self__ .__class__
87
- except AttributeError :
88
- self .inst = None
89
- self .func = cb
90
- self .klass = None
91
-
92
- def add_destroy_callback (self , callback ):
93
- self ._destroy_callbacks .append (_BoundMethodProxy (callback ))
94
-
95
- def _destroy (self , wk ):
96
- for callback in self ._destroy_callbacks :
97
- try :
98
- callback (self )
99
- except ReferenceError :
100
- pass
101
-
102
- def __getstate__ (self ):
103
- d = self .__dict__ .copy ()
104
- # de-weak reference inst
105
- inst = d ['inst' ]
106
- if inst is not None :
107
- d ['inst' ] = inst ()
108
- return d
109
-
110
- def __setstate__ (self , statedict ):
111
- self .__dict__ = statedict
112
- inst = statedict ['inst' ]
113
- # turn inst back into a weakref
114
- if inst is not None :
115
- self .inst = ref (inst )
116
-
117
- def __call__ (self , * args , ** kwargs ):
118
- """
119
- Proxy for a call to the weak referenced object. Take
120
- arbitrary params to pass to the callable.
121
-
122
- Raises `ReferenceError`: When the weak reference refers to
123
- a dead object
124
- """
125
- if self .inst is not None and self .inst () is None :
126
- raise ReferenceError
127
- elif self .inst is not None :
128
- # build a new instance method with a strong reference to the
129
- # instance
130
74
131
- mtd = types .MethodType (self .func , self .inst ())
75
+ def __init__ (self , obj ):
76
+ self ._obj = obj
132
77
133
- else :
134
- # not a bound method, just return the func
135
- mtd = self .func
136
- # invoke the callable and return the result
137
- return mtd (* args , ** kwargs )
78
+ def __call__ (self ):
79
+ return self ._obj
138
80
139
81
def __eq__ (self , other ):
140
- """
141
- Compare the held function and instance with that held by
142
- another proxy.
143
- """
144
- try :
145
- if self .inst is None :
146
- return self .func == other .func and other .inst is None
147
- else :
148
- return self .func == other .func and self .inst () == other .inst ()
149
- except Exception :
150
- return False
82
+ return isinstance (other , _StrongRef ) and self ._obj == other ._obj
151
83
152
84
def __hash__ (self ):
153
- return self ._hash
154
-
155
-
156
- def _exception_printer (exc ):
157
- traceback .print_exc ()
85
+ return hash (self ._obj )
158
86
159
87
160
88
class CallbackRegistry (object ):
@@ -179,20 +107,13 @@ class CallbackRegistry(object):
179
107
>>> callbacks.disconnect(id_eat)
180
108
>>> callbacks.process('eat', 456) # nothing will be called
181
109
182
- In practice, one should always disconnect all callbacks when they
183
- are no longer needed to avoid dangling references (and thus memory
184
- leaks). However, real code in matplotlib rarely does so, and due
185
- to its design, it is rather difficult to place this kind of code.
186
- To get around this, and prevent this class of memory leaks, we
187
- instead store weak references to bound methods only, so when the
188
- destination object needs to die, the CallbackRegistry won't keep
189
- it alive. The Python stdlib weakref module can not create weak
190
- references to bound methods directly, so we need to create a proxy
191
- object to handle weak references to bound methods (or regular free
192
- functions). This technique was shared by Peter Parente on his
193
- `"Mindtrove" blog
194
- <http://mindtrove.info/python-weak-references/>`_.
195
-
110
+ In practice, one should always disconnect all callbacks when they are
111
+ no longer needed to avoid dangling references (and thus memory leaks).
112
+ However, real code in Matplotlib rarely does so, and due to its design,
113
+ it is rather difficult to place this kind of code. To get around this,
114
+ and prevent this class of memory leaks, we instead store weak references
115
+ to bound methods only, so when the destination object needs to die, the
116
+ CallbackRegistry won't keep it alive.
196
117
197
118
Parameters
198
119
----------
@@ -211,12 +132,17 @@ def handler(exc: Exception) -> None:
211
132
212
133
def h(exc):
213
134
traceback.print_exc()
214
-
215
135
"""
136
+
137
+ # We maintain two mappings:
138
+ # callbacks: signal -> {cid -> callback}
139
+ # _func_cid_map: signal -> {callback -> cid}
140
+ # (actually, callbacks are weakrefs to the actual callbacks).
141
+
216
142
def __init__ (self , exception_handler = _exception_printer ):
217
143
self .exception_handler = exception_handler
218
- self .callbacks = dict ()
219
- self ._cid = 0
144
+ self .callbacks = {}
145
+ self ._cid_gen = itertools . count ()
220
146
self ._func_cid_map = {}
221
147
222
148
# In general, callbacks may not be pickled; thus, we simply recreate an
@@ -236,18 +162,17 @@ def __setstate__(self, state):
236
162
def connect (self , s , func ):
237
163
"""Register *func* to be called when signal *s* is generated.
238
164
"""
239
- self ._func_cid_map .setdefault (s , WeakKeyDictionary ())
240
- # Note proxy not needed in python 3.
241
- # TODO rewrite this when support for python2.x gets dropped.
242
- proxy = _BoundMethodProxy (func )
165
+ self ._func_cid_map .setdefault (s , {})
166
+ try :
167
+ proxy = WeakMethod (func , self ._remove_proxy )
168
+ except TypeError :
169
+ proxy = _StrongRef (func )
243
170
if proxy in self ._func_cid_map [s ]:
244
171
return self ._func_cid_map [s ][proxy ]
245
172
246
- proxy .add_destroy_callback (self ._remove_proxy )
247
- self ._cid += 1
248
- cid = self ._cid
173
+ cid = next (self ._cid_gen )
249
174
self ._func_cid_map [s ][proxy ] = cid
250
- self .callbacks .setdefault (s , dict () )
175
+ self .callbacks .setdefault (s , {} )
251
176
self .callbacks [s ][cid ] = proxy
252
177
return cid
253
178
@@ -257,7 +182,6 @@ def _remove_proxy(self, proxy):
257
182
del self .callbacks [signal ][proxies [proxy ]]
258
183
except KeyError :
259
184
pass
260
-
261
185
if len (self .callbacks [signal ]) == 0 :
262
186
del self .callbacks [signal ]
263
187
del self ._func_cid_map [signal ]
@@ -284,12 +208,11 @@ def process(self, s, *args, **kwargs):
284
208
All of the functions registered to receive callbacks on *s* will be
285
209
called with ``*args`` and ``**kwargs``.
286
210
"""
287
- if s in self .callbacks :
288
- for cid , proxy in list (self .callbacks [s ].items ()):
211
+ for cid , ref in list (self .callbacks .get (s , {}).items ()):
212
+ func = ref ()
213
+ if func is not None :
289
214
try :
290
- proxy (* args , ** kwargs )
291
- except ReferenceError :
292
- self ._remove_proxy (proxy )
215
+ func (* args , ** kwargs )
293
216
# this does not capture KeyboardInterrupt, SystemExit,
294
217
# and GeneratorExit
295
218
except Exception as exc :
@@ -979,10 +902,10 @@ class Grouper(object):
979
902
980
903
"""
981
904
def __init__ (self , init = ()):
982
- self ._mapping = {ref (x ): [ref (x )] for x in init }
905
+ self ._mapping = {weakref . ref (x ): [weakref . ref (x )] for x in init }
983
906
984
907
def __contains__ (self , item ):
985
- return ref (item ) in self ._mapping
908
+ return weakref . ref (item ) in self ._mapping
986
909
987
910
def clean (self ):
988
911
"""Clean dead weak references from the dictionary."""
@@ -997,10 +920,10 @@ def join(self, a, *args):
997
920
Join given arguments into the same set. Accepts one or more arguments.
998
921
"""
999
922
mapping = self ._mapping
1000
- set_a = mapping .setdefault (ref (a ), [ref (a )])
923
+ set_a = mapping .setdefault (weakref . ref (a ), [weakref . ref (a )])
1001
924
1002
925
for arg in args :
1003
- set_b = mapping .get (ref (arg ), [ref (arg )])
926
+ set_b = mapping .get (weakref . ref (arg ), [weakref . ref (arg )])
1004
927
if set_b is not set_a :
1005
928
if len (set_b ) > len (set_a ):
1006
929
set_a , set_b = set_b , set_a
@@ -1013,13 +936,14 @@ def join(self, a, *args):
1013
936
def joined (self , a , b ):
1014
937
"""Returns True if *a* and *b* are members of the same set."""
1015
938
self .clean ()
1016
- return self ._mapping .get (ref (a ), object ()) is self ._mapping .get (ref (b ))
939
+ return (self ._mapping .get (weakref .ref (a ), object ())
940
+ is self ._mapping .get (weakref .ref (b )))
1017
941
1018
942
def remove (self , a ):
1019
943
self .clean ()
1020
- set_a = self ._mapping .pop (ref (a ), None )
944
+ set_a = self ._mapping .pop (weakref . ref (a ), None )
1021
945
if set_a :
1022
- set_a .remove (ref (a ))
946
+ set_a .remove (weakref . ref (a ))
1023
947
1024
948
def __iter__ (self ):
1025
949
"""
@@ -1035,7 +959,7 @@ def __iter__(self):
1035
959
def get_siblings (self , a ):
1036
960
"""Returns all of the items joined with *a*, including itself."""
1037
961
self .clean ()
1038
- siblings = self ._mapping .get (ref (a ), [ref (a )])
962
+ siblings = self ._mapping .get (weakref . ref (a ), [weakref . ref (a )])
1039
963
return [x () for x in siblings ]
1040
964
1041
965
@@ -1254,7 +1178,7 @@ def _compute_conf_interval(data, med, iqr, bootstrap):
1254
1178
1255
1179
ncols = len (X )
1256
1180
if labels is None :
1257
- labels = repeat (None )
1181
+ labels = itertools . repeat (None )
1258
1182
elif len (labels ) != ncols :
1259
1183
raise ValueError ("Dimensions of labels and X must be compatible" )
1260
1184
0 commit comments