4343from __future__ import print_function
4444
4545import operator
46+ import opcode
4647import os
4748import io
4849import pickle
5354import itertools
5455import dis
5556import traceback
57+ import weakref
58+
5659
5760if sys .version < '3' :
5861 from pickle import Pickler
6871 PY3 = True
6972
7073#relevant opcodes
71- STORE_GLOBAL = dis . opname . index ( 'STORE_GLOBAL' )
72- DELETE_GLOBAL = dis . opname . index ( 'DELETE_GLOBAL' )
73- LOAD_GLOBAL = dis . opname . index ( 'LOAD_GLOBAL' )
74- GLOBAL_OPS = [ STORE_GLOBAL , DELETE_GLOBAL , LOAD_GLOBAL ]
74+ STORE_GLOBAL = opcode . opmap [ 'STORE_GLOBAL' ]
75+ DELETE_GLOBAL = opcode . opmap [ 'DELETE_GLOBAL' ]
76+ LOAD_GLOBAL = opcode . opmap [ 'LOAD_GLOBAL' ]
77+ GLOBAL_OPS = ( STORE_GLOBAL , DELETE_GLOBAL , LOAD_GLOBAL )
7578HAVE_ARGUMENT = dis .HAVE_ARGUMENT
7679EXTENDED_ARG = dis .EXTENDED_ARG
7780
@@ -90,6 +93,43 @@ def _builtin_type(name):
9093 return getattr (types , name )
9194
9295
96+ if sys .version_info < (3 , 4 ):
97+ def _walk_global_ops (code ):
98+ """
99+ Yield (opcode, argument number) tuples for all
100+ global-referencing instructions in *code*.
101+ """
102+ code = getattr (code , 'co_code' , b'' )
103+ if not PY3 :
104+ code = map (ord , code )
105+
106+ n = len (code )
107+ i = 0
108+ extended_arg = 0
109+ while i < n :
110+ op = code [i ]
111+ i += 1
112+ if op >= HAVE_ARGUMENT :
113+ oparg = code [i ] + code [i + 1 ] * 256 + extended_arg
114+ extended_arg = 0
115+ i += 2
116+ if op == EXTENDED_ARG :
117+ extended_arg = oparg * 65536
118+ if op in GLOBAL_OPS :
119+ yield op , oparg
120+
121+ else :
122+ def _walk_global_ops (code ):
123+ """
124+ Yield (opcode, argument number) tuples for all
125+ global-referencing instructions in *code*.
126+ """
127+ for instr in dis .get_instructions (code ):
128+ op = instr .opcode
129+ if op in GLOBAL_OPS :
130+ yield op , instr .arg
131+
132+
93133class CloudPickler (Pickler ):
94134
95135 dispatch = Pickler .dispatch .copy ()
@@ -250,38 +290,34 @@ def save_function_tuple(self, func):
250290 write (pickle .TUPLE )
251291 write (pickle .REDUCE ) # applies _fill_function on the tuple
252292
253- @staticmethod
254- def extract_code_globals (co ):
293+ _extract_code_globals_cache = (
294+ weakref .WeakKeyDictionary ()
295+ if sys .version_info >= (2 , 7 ) and not hasattr (sys , "pypy_version_info" )
296+ else {})
297+
298+ @classmethod
299+ def extract_code_globals (cls , co ):
255300 """
256301 Find all globals names read or written to by codeblock co
257302 """
258- code = co .co_code
259- if not PY3 :
260- code = [ord (c ) for c in code ]
261- names = co .co_names
262- out_names = set ()
263-
264- n = len (code )
265- i = 0
266- extended_arg = 0
267- while i < n :
268- op = code [i ]
303+ out_names = cls ._extract_code_globals_cache .get (co )
304+ if out_names is None :
305+ try :
306+ names = co .co_names
307+ except AttributeError :
308+ # PyPy "builtin-code" object
309+ out_names = set ()
310+ else :
311+ out_names = set (names [oparg ]
312+ for op , oparg in _walk_global_ops (co ))
269313
270- i += 1
271- if op >= HAVE_ARGUMENT :
272- oparg = code [i ] + code [i + 1 ] * 256 + extended_arg
273- extended_arg = 0
274- i += 2
275- if op == EXTENDED_ARG :
276- extended_arg = oparg * 65536
277- if op in GLOBAL_OPS :
278- out_names .add (names [oparg ])
314+ # see if nested function have any global refs
315+ if co .co_consts :
316+ for const in co .co_consts :
317+ if type (const ) is types .CodeType :
318+ out_names |= cls .extract_code_globals (const )
279319
280- # see if nested function have any global refs
281- if co .co_consts :
282- for const in co .co_consts :
283- if type (const ) is types .CodeType :
284- out_names |= CloudPickler .extract_code_globals (const )
320+ cls ._extract_code_globals_cache [co ] = out_names
285321
286322 return out_names
287323
0 commit comments