def func2clean_signature_str(func) ->\ 'without __annotations__, __defaults__, __kwdefaults__': ## | function(code, globals[, name[, argdefs[, closure]]]) ## | ## | Create a function object from a code object and a dictionary. ## | The optional name string overrides the name from the code object. ## | The optional argdefs tuple specifies the default argument values. ## | The optional closure tuple supplies the bindings for free variables. if 1: code = func.__code__ closure = code.co_freevars try: _clean = FunctionType(code, {}, closure=func.__closure__) except: print(func.__closure__) raise else: # fail when func contains freevars: def _clean(): # to provide a clean env to extract signature string # used by func2code_signature_str pass _clean.__code__ = func.__code__ sig = signature(_clean) return str(sig)
def _update_function(oldfunc: FunctionType, newfunc: FunctionType): """Update a function object.""" logger.info(f"Patch function {oldfunc.__qualname__}") oldfunc.__doc__ = newfunc.__doc__ oldfunc.__dict__.update(newfunc.__dict__) oldfunc.__annotations__ = newfunc.__annotations__ oldfunc.__code__ = newfunc.__code__ oldfunc.__defaults__ = newfunc.__defaults__
def conform(self, obj1: types.FunctionType, obj2: types.FunctionType): fv1 = obj1.__code__.co_freevars fv2 = obj2.__code__.co_freevars if fv1 != fv2: msg = ( f"Cannot replace closure `{obj1.__name__}` because the free " f"variables changed. Before: {fv1}; after: {fv2}." ) if ("__class__" in (fv1 or ())) ^ ("__class__" in (fv2 or ())): msg += " Note: The use of `super` entails the `__class__` free variable." raise ConformException(msg) obj1.__code__ = obj2.__code__ obj1.__defaults__ = obj2.__defaults__ obj1.__kwdefaults__ = obj2.__kwdefaults__
def inline(pre_func: FunctionType, func: FunctionType, pre_func_arguments: dict): """Insert `prefunc` at the beginning of `func` and return the corresponding function. `pre_func` should not have a return statement (else a ValueError is raised). `pre_func_arguments` keys should be identical as `pre_func` arguments names else a TypeError is raised. This approach takes less CPU instructions than the standard decorator approach. Example: def pre_func(b, c): a = "hello" print(a + " " + b + " " + c) def func(x, y): z = x + 2 * y return z ** 2 The returned function corresponds to: def inlined(x, y): a = "hello" print(a) z = x + 2 * y return z ** 2 """ new_func = FunctionType( func.__code__, func.__globals__, func.__name__, func.__defaults__, func.__closure__, ) if not has_no_return(pre_func): raise ValueError("`pre_func` returns something") pinned_pre_func = pin_arguments(pre_func, pre_func_arguments) pinned_pre_func_code = pinned_pre_func.__code__ pinned_pre_func_co_consts = pinned_pre_func_code.co_consts pinned_pre_func_co_names = pinned_pre_func_code.co_names pinned_pre_func_co_varnames = pinned_pre_func_code.co_varnames pinned_pre_func_instructions = tuple(get_instructions(pinned_pre_func)) pinned_pre_func_instructions_without_return = pinned_pre_func_instructions[: -2] func_code = func.__code__ func_co_consts = func_code.co_consts func_co_names = func_code.co_names func_co_varnames = func_code.co_varnames func_instructions = tuple(get_instructions(func)) shifted_func_instructions = shift_instructions( func_instructions, len(b"".join(pinned_pre_func_instructions_without_return))) new_co_consts = remove_duplicates(func_co_consts + pinned_pre_func_co_consts) new_co_names = remove_duplicates(func_co_names + pinned_pre_func_co_names) new_co_varnames = remove_duplicates(func_co_varnames + pinned_pre_func_co_varnames) trans_co_consts = get_transitions(pinned_pre_func_co_consts, new_co_consts) trans_co_names = get_transitions(pinned_pre_func_co_names, new_co_names) trans_co_varnames = get_transitions(pinned_pre_func_co_varnames, new_co_varnames) transitions = { **get_b_transitions(trans_co_consts, OpCode.LOAD_CONST, OpCode.LOAD_CONST), **get_b_transitions(trans_co_names, OpCode.LOAD_GLOBAL, OpCode.LOAD_GLOBAL), **get_b_transitions(trans_co_names, OpCode.LOAD_METHOD, OpCode.LOAD_METHOD), **get_b_transitions(trans_co_names, OpCode.LOAD_ATTR, OpCode.LOAD_ATTR), **get_b_transitions(trans_co_names, OpCode.STORE_ATTR, OpCode.STORE_ATTR), **get_b_transitions(trans_co_varnames, OpCode.LOAD_FAST, OpCode.LOAD_FAST), **get_b_transitions(trans_co_varnames, OpCode.STORE_FAST, OpCode.STORE_FAST), } new_pinned_pre_func_instructions = tuple( transitions.get(instruction, instruction) for instruction in pinned_pre_func_instructions_without_return) new_instructions = new_pinned_pre_func_instructions + shifted_func_instructions new_co_code = b"".join(new_instructions) nfcode = new_func.__code__ python_version = sys.version_info if python_version.minor != 8: new_func.__code__ = CodeType( nfcode.co_argcount, nfcode.co_kwonlyargcount, len(new_co_varnames), nfcode.co_stacksize, nfcode.co_flags, new_co_code, new_co_consts, new_co_names, new_co_varnames, nfcode.co_filename, nfcode.co_name, nfcode.co_firstlineno, nfcode.co_lnotab, nfcode.co_freevars, nfcode.co_cellvars, ) return new_func new_func.__code__ = CodeType( nfcode.co_argcount, nfcode.co_posonlyargcount, nfcode.co_kwonlyargcount, len(new_co_varnames), nfcode.co_stacksize, nfcode.co_flags, new_co_code, new_co_consts, new_co_names, new_co_varnames, nfcode.co_filename, nfcode.co_name, nfcode.co_firstlineno, nfcode.co_lnotab, nfcode.co_freevars, nfcode.co_cellvars, ) return new_func
def pin_arguments(func: FunctionType, arguments: dict): """Transform `func` in a function with no arguments. Example: def func(a, b): c = 4 print(str(a) + str(c)) return b The function returned by pin_arguments(func, {"a": 10, "b": 11}) is equivalent to: def pinned_func(): c = 4 print(str(10) + str(c)) return 11 This function is in some ways equivalent to functools.partials but with a faster runtime. `arguments` keys should be identical as `func` arguments names else a TypeError is raised. """ if signature(func).parameters.keys() != set(arguments): raise TypeError("`arguments` and `func` arguments do not correspond") func_code = func.__code__ func_co_consts = func_code.co_consts func_co_varnames = func_code.co_varnames new_co_consts = remove_duplicates(func_co_consts + tuple(arguments.values())) new_co_varnames = tuple(item for item in func_co_varnames if item not in arguments) trans_co_varnames2_co_consts = { func_co_varnames.index(key): new_co_consts.index(value) for key, value in arguments.items() } trans_co_varnames = get_transitions(func_co_varnames, new_co_varnames) transitions = { **get_b_transitions(trans_co_varnames2_co_consts, OpCode.LOAD_FAST, OpCode.LOAD_CONST), **get_b_transitions(trans_co_varnames, OpCode.LOAD_FAST, OpCode.LOAD_FAST), **get_b_transitions(trans_co_varnames, OpCode.STORE_FAST, OpCode.STORE_FAST), } func_instructions = get_instructions(func) new_func_instructions = tuple( transitions.get(instruction, instruction) for instruction in func_instructions) new_co_code = b"".join(new_func_instructions) new_func = FunctionType( func.__code__, func.__globals__, func.__name__, func.__defaults__, func.__closure__, ) nfcode = new_func.__code__ python_version = sys.version_info if python_version.minor != 8: new_func.__code__ = CodeType( 0, 0, len(new_co_varnames), nfcode.co_stacksize, nfcode.co_flags, new_co_code, new_co_consts, nfcode.co_names, new_co_varnames, nfcode.co_filename, nfcode.co_name, nfcode.co_firstlineno, nfcode.co_lnotab, nfcode.co_freevars, nfcode.co_cellvars, ) return new_func new_func.__code__ = CodeType( 0, 0, 0, len(new_co_varnames), nfcode.co_stacksize, nfcode.co_flags, new_co_code, new_co_consts, nfcode.co_names, new_co_varnames, nfcode.co_filename, nfcode.co_name, nfcode.co_firstlineno, nfcode.co_lnotab, nfcode.co_freevars, nfcode.co_cellvars, ) return new_func
def allow_goto(f: types.FunctionType): assert isinstance( f, types.FunctionType), "expect a function, got a {}".format(f) f.__code__ = _allow_goto(f.__code__, f.__globals__) return f