""" Oren Tirosh Convert code objects (functions bodies only) to source code and back. This doesn't actually decompile the bytecode - it simply fetches the source code from the .py file and then carefully compiles it back to a 100% identical code object: c == recompile(*uncompile(c)) Not supported: Lambdas Nested functions (you can still process the function containing them) Anything for which inspect.getsource can't get the source de """ import ast, inspect, re from types import CodeType as code, FunctionType as function import __future__ PyCF_MASK = sum(v for k, v in vars(__future__).items() if k.startswith('CO_FUTURE')) class Error(Exception): pass class Unsupported(Error): pass class NoSource(Error): pass def uncompile(c): """ uncompile(codeobj) -> [source, filename, mode, flags, firstlineno, privateprefix] """ if c.co_flags & inspect.CO_NESTED or c.co_freevars: raise Unsupported('nested functions not supported') if c.co_name == '': raise Unsupported('lambda functions not supported') if c.co_filename == '': raise Unsupported('code without source file not supported') filename = inspect.getfile(c) try: lines, firstlineno = inspect.getsourcelines(c) except IOError: raise NoSource('source code not available') source = ''.join(lines) # __X is mangled to _ClassName__X in methods. Find this prefix: privateprefix = None for name in c.co_names: m = re.match('^(_[A-Za-z][A-Za-z0-9_]*)__.*$', name) if m: privateprefix = m.group(1) break return [source, filename, 'exec', c.co_flags & PyCF_MASK, firstlineno, privateprefix] def recompile(source, filename, mode, flags=0, firstlineno=1, privateprefix=None): """ recompile output of uncompile back to a code object. source may also be preparsed AST """ if isinstance(source, ast.AST): a = source else: a = parse_snippet(source, filename, mode, flags, firstlineno) node = a.body[0] if not isinstance(node, ast.FunctionDef): raise Error('Expecting function AST node') c0 = compile(a, filename, mode, flags, True) # This code object defines the function. Find the function's actual body code: for c in c0.co_consts: if not isinstance(c, code): continue if c.co_name == node.name and c.co_firstlineno == node.lineno: break else: raise Error('Function body code not found') # Re-mangle private names: if privateprefix is not None: def fixnames(names): isprivate = re.compile('^__.*(?