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,20 @@ 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
-
131
- mtd = types .MethodType (self .func , self .inst ())
132
-
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 )
138
-
139
- 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
151
-
152
- def __hash__ (self ):
153
- return self ._hash
154
74
75
+ def __init__ (self , obj ):
76
+ self ._obj = obj
155
77
156
- def _exception_printer ( exc ):
157
- traceback . print_exc ()
78
+ def __call__ ( self ):
79
+ return self . _obj
158
80
159
81
160
82
class CallbackRegistry (object ):
@@ -179,20 +101,13 @@ class CallbackRegistry(object):
179
101
>>> callbacks.disconnect(id_eat)
180
102
>>> callbacks.process('eat', 456) # nothing will be called
181
103
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
-
104
+ In practice, one should always disconnect all callbacks when they are
105
+ no longer needed to avoid dangling references (and thus memory leaks).
106
+ However, real code in Matplotlib rarely does so, and due to its design,
107
+ it is rather difficult to place this kind of code. To get around this,
108
+ and prevent this class of memory leaks, we instead store weak references
109
+ to bound methods only, so when the destination object needs to die, the
110
+ CallbackRegistry won't keep it alive.
196
111
197
112
Parameters
198
113
----------
@@ -211,12 +126,17 @@ def handler(exc: Exception) -> None:
211
126
212
127
def h(exc):
213
128
traceback.print_exc()
214
-
215
129
"""
130
+
131
+ # We maintain two mappings:
132
+ # callbacks: signal -> {cid -> callback}
133
+ # _func_cid_map: signal -> {callback -> cid}
134
+ # (actually, callbacks are weakrefs to the actual callbacks).
135
+
216
136
def __init__ (self , exception_handler = _exception_printer ):
217
137
self .exception_handler = exception_handler
218
138
self .callbacks = dict ()
219
- self ._cid = 0
139
+ self ._cid_gen = itertools . count ()
220
140
self ._func_cid_map = {}
221
141
222
142
# In general, callbacks may not be pickled; thus, we simply recreate an
@@ -236,18 +156,16 @@ def __setstate__(self, state):
236
156
def connect (self , s , func ):
237
157
"""Register *func* to be called when signal *s* is generated.
238
158
"""
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 )
159
+ self ._func_cid_map .setdefault (s , {} )
160
+ proxy = ( WeakMethod ( func , self . _remove_proxy )
161
+ if inspect . ismethod ( func )
162
+ else _StrongRef (func ) )
243
163
if proxy in self ._func_cid_map [s ]:
244
164
return self ._func_cid_map [s ][proxy ]
245
165
246
- proxy .add_destroy_callback (self ._remove_proxy )
247
- self ._cid += 1
248
- cid = self ._cid
166
+ cid = next (self ._cid_gen )
249
167
self ._func_cid_map [s ][proxy ] = cid
250
- self .callbacks .setdefault (s , dict () )
168
+ self .callbacks .setdefault (s , {} )
251
169
self .callbacks [s ][cid ] = proxy
252
170
return cid
253
171
@@ -257,7 +175,6 @@ def _remove_proxy(self, proxy):
257
175
del self .callbacks [signal ][proxies [proxy ]]
258
176
except KeyError :
259
177
pass
260
-
261
178
if len (self .callbacks [signal ]) == 0 :
262
179
del self .callbacks [signal ]
263
180
del self ._func_cid_map [signal ]
@@ -284,12 +201,11 @@ def process(self, s, *args, **kwargs):
284
201
All of the functions registered to receive callbacks on *s* will be
285
202
called with ``*args`` and ``**kwargs``.
286
203
"""
287
- if s in self .callbacks :
288
- for cid , proxy in list (self .callbacks [s ].items ()):
204
+ for cid , ref in list (self .callbacks .get (s , {}).items ()):
205
+ func = ref ()
206
+ if func is not None :
289
207
try :
290
- proxy (* args , ** kwargs )
291
- except ReferenceError :
292
- self ._remove_proxy (proxy )
208
+ func (* args , ** kwargs )
293
209
# this does not capture KeyboardInterrupt, SystemExit,
294
210
# and GeneratorExit
295
211
except Exception as exc :
@@ -979,10 +895,10 @@ class Grouper(object):
979
895
980
896
"""
981
897
def __init__ (self , init = ()):
982
- self ._mapping = {ref (x ): [ref (x )] for x in init }
898
+ self ._mapping = {weakref . ref (x ): [weakref . ref (x )] for x in init }
983
899
984
900
def __contains__ (self , item ):
985
- return ref (item ) in self ._mapping
901
+ return weakref . ref (item ) in self ._mapping
986
902
987
903
def clean (self ):
988
904
"""Clean dead weak references from the dictionary."""
@@ -997,10 +913,10 @@ def join(self, a, *args):
997
913
Join given arguments into the same set. Accepts one or more arguments.
998
914
"""
999
915
mapping = self ._mapping
1000
- set_a = mapping .setdefault (ref (a ), [ref (a )])
916
+ set_a = mapping .setdefault (weakref . ref (a ), [weakref . ref (a )])
1001
917
1002
918
for arg in args :
1003
- set_b = mapping .get (ref (arg ), [ref (arg )])
919
+ set_b = mapping .get (weakref . ref (arg ), [weakref . ref (arg )])
1004
920
if set_b is not set_a :
1005
921
if len (set_b ) > len (set_a ):
1006
922
set_a , set_b = set_b , set_a
@@ -1013,13 +929,14 @@ def join(self, a, *args):
1013
929
def joined (self , a , b ):
1014
930
"""Returns True if *a* and *b* are members of the same set."""
1015
931
self .clean ()
1016
- return self ._mapping .get (ref (a ), object ()) is self ._mapping .get (ref (b ))
932
+ return (self ._mapping .get (weakref .ref (a ), object ())
933
+ is self ._mapping .get (weakref .ref (b )))
1017
934
1018
935
def remove (self , a ):
1019
936
self .clean ()
1020
- set_a = self ._mapping .pop (ref (a ), None )
937
+ set_a = self ._mapping .pop (weakref . ref (a ), None )
1021
938
if set_a :
1022
- set_a .remove (ref (a ))
939
+ set_a .remove (weakref . ref (a ))
1023
940
1024
941
def __iter__ (self ):
1025
942
"""
@@ -1035,7 +952,7 @@ def __iter__(self):
1035
952
def get_siblings (self , a ):
1036
953
"""Returns all of the items joined with *a*, including itself."""
1037
954
self .clean ()
1038
- siblings = self ._mapping .get (ref (a ), [ref (a )])
955
+ siblings = self ._mapping .get (weakref . ref (a ), [weakref . ref (a )])
1039
956
return [x () for x in siblings ]
1040
957
1041
958
@@ -1254,7 +1171,7 @@ def _compute_conf_interval(data, med, iqr, bootstrap):
1254
1171
1255
1172
ncols = len (X )
1256
1173
if labels is None :
1257
- labels = repeat (None )
1174
+ labels = itertools . repeat (None )
1258
1175
elif len (labels ) != ncols :
1259
1176
raise ValueError ("Dimensions of labels and X must be compatible" )
1260
1177
0 commit comments