3
3
from ctypes import py_object , pythonapi
4
4
from itertools import chain
5
5
from types import CodeType , FunctionType
6
+ from weakref import WeakKeyDictionary
7
+
8
+ try :
9
+ import threading
10
+ except ImportError :
11
+ import dummy_threading as threading
6
12
7
13
from .code import Code
8
14
from .instructions import LOAD_CONST , STORE_FAST , LOAD_FAST
11
17
patterndispatcher ,
12
18
DEFAULT_STARTCODE ,
13
19
)
20
+ from .utils .instance import instance
14
21
15
22
16
23
_cell_new = pythonapi .PyCell_New
@@ -48,7 +55,18 @@ class NoContext(Exception):
48
55
attribute was accessed outside of a code context.
49
56
"""
50
57
def __init__ (self ):
51
- return super ().__init__ ('no context' )
58
+ return super ().__init__ ('no active transformation context' )
59
+
60
+
61
+ class Context :
62
+ """Empty object for holding the transformation context.
63
+ """
64
+ def __init__ (self , code ):
65
+ self .code = code
66
+ self .startcode = DEFAULT_STARTCODE
67
+
68
+ def __repr__ (self ): # pragma: no cover
69
+ return '<%s: %r>' % (type (self ).__name__ , self .__dict__ )
52
70
53
71
54
72
class CodeTransformerMeta (type ):
@@ -81,11 +99,7 @@ class CodeTransformer(metaclass=CodeTransformerMeta):
81
99
----------
82
100
code
83
101
"""
84
- __slots__ = '_code_stack' , '_startcode_stack'
85
-
86
- def __init__ (self ):
87
- self ._code_stack = []
88
- self ._startcode_stack = []
102
+ __slots__ = '__weakref__' ,
89
103
90
104
def transform_consts (self , consts ):
91
105
"""transformer for the co_consts field.
@@ -186,9 +200,7 @@ def transform(self, code, *, name=None, filename=None):
186
200
filename = filename if filename is not None else code .filename ,
187
201
firstlineno = code .firstlineno ,
188
202
lnotab = _new_lnotab (post_transform , code .lnotab ),
189
- nested = code .is_nested ,
190
- coroutine = code .is_coroutine ,
191
- iterable_coroutine = code .is_iterable_coroutine ,
203
+ flags = code .flags ,
192
204
)
193
205
194
206
def __call__ (self , f , * ,
@@ -207,33 +219,67 @@ def __call__(self, f, *,
207
219
closure ,
208
220
)
209
221
222
+ @instance
223
+ class _context_stack (threading .local ):
224
+ """Thread safe transformation context stack.
225
+
226
+ Each thread will get it's own ``WeakKeyDictionary`` that maps
227
+ instances to a stack of ``Context`` objects. When this descriptor
228
+ is looked up we first try to get the weakkeydict off of the thread
229
+ local storage. If it doesn't exist we make a new map. Then we lookup
230
+ our instance in this map. If it doesn't exist yet create a new stack
231
+ (as an empty list).
232
+
233
+ This allows a single instance of ``CodeTransformer`` to be used
234
+ recursively to transform code objects in a thread safe way while
235
+ still being able to use a stateful context.
236
+ """
237
+ def __get__ (self , instance , owner ):
238
+ try :
239
+ stacks = self ._context_stacks
240
+ except AttributeError :
241
+ stacks = self ._context_stacks = WeakKeyDictionary ()
242
+
243
+ if instance is None :
244
+ # when looked up off the class return the current threads
245
+ # context stacks map
246
+ return stacks
247
+
248
+ return stacks .setdefault (instance , [])
249
+
210
250
@contextmanager
211
251
def _new_context (self , code ):
212
- self ._code_stack .append (code )
213
- self ._startcode_stack .append (DEFAULT_STARTCODE )
252
+ self ._context_stack .append (Context (code ))
214
253
try :
215
254
yield
216
255
finally :
217
- self ._code_stack .pop ()
218
- self ._startcode_stack .pop ()
256
+ self ._context_stack .pop ()
219
257
220
258
@property
221
- def code (self ):
222
- """The code object we are currently manipulating.
259
+ def context (self ):
260
+ """Lookup the current transformation context.
261
+
262
+ Raises
263
+ ------
264
+ NoContext
265
+ Raised when there is no active transformation context.
223
266
"""
224
267
try :
225
- return self ._code_stack [- 1 ]
268
+ return self ._context_stack [- 1 ]
226
269
except IndexError :
227
270
raise NoContext ()
228
271
272
+ @property
273
+ def code (self ):
274
+ """The code object we are currently manipulating.
275
+ """
276
+ return self .context .code
277
+
229
278
@property
230
279
def startcode (self ):
231
280
"""The startcode we are currently in.
232
281
"""
233
- try :
234
- return self ._startcode_stack [- 1 ]
235
- except IndexError :
236
- raise NoContext ()
282
+ return self .context .startcode
237
283
238
284
def begin (self , startcode ):
239
285
"""Begin a new startcode.
@@ -243,9 +289,4 @@ def begin(self, startcode):
243
289
startcode : any
244
290
The startcode to begin.
245
291
"""
246
- try :
247
- # "beginning" a new startcode changes the current startcode.
248
- # Here we are mutating the current context's startcode.
249
- self ._startcode_stack [- 1 ] = startcode
250
- except IndexError :
251
- raise NoContext ()
292
+ self .context .startcode = startcode
0 commit comments