def copy_func(func: FunctionType, deep: bool = False) -> FunctionType: """Copy a function as a different object. Args: func: Function object to be copied. deep: If ``True``, mutable attributes of ``func`` are deep-copied. Returns: Function as a different object from the original one. """ copied = FunctionType( func.__code__, func.__globals__, func.__name__, func.__defaults__, func.__closure__, ) # mutable attributes are copied by the given method copier = deepcopy if deep else copy copied.__annotations__ = copier(func.__annotations__) copied.__dict__ = copier(func.__dict__) copied.__kwdefaults__ = copier(func.__kwdefaults__) # immutable attributes are not copied (just assigned) copied.__doc__ = func.__doc__ copied.__module__ = func.__module__ copied.__name__ = func.__name__ copied.__qualname__ = func.__qualname__ return copied
def rename_function(fn, newname): """Create a copy of the function with a different name.""" co = fn.__code__ extra_args = [] if hasattr(co, "co_posonlyargcount"): # pragma: no cover extra_args.append(co.co_posonlyargcount) newcode = type(co)( co.co_argcount, *extra_args, co.co_kwonlyargcount, co.co_nlocals, co.co_stacksize, co.co_flags, co.co_code, co.co_consts, co.co_names, co.co_varnames, co.co_filename, newname, co.co_firstlineno, co.co_lnotab, co.co_freevars, co.co_cellvars, ) new_fn = FunctionType(newcode, fn.__globals__, newname, fn.__defaults__, fn.__closure__) new_fn.__annotations__ = fn.__annotations__ return new_fn
def loads_function(s): '''Restores a function serialized with :func:`dumps_function`.''' if PY2: name, code, globals_, defaults, closure, func_dict, doc = loads(s) else: name, code, globals_, defaults, closure, func_dict, doc, qualname, kwdefaults, annotations = loads( s) code = marshal.loads(code) for k, v in globals_.items(): if isinstance(v, Module): globals_[k] = v.mod if closure is not None: import ctypes ctypes.pythonapi.PyCell_New.restype = ctypes.py_object ctypes.pythonapi.PyCell_New.argtypes = [ctypes.py_object] closure = tuple(ctypes.pythonapi.PyCell_New(c) for c in closure) globals_['__builtins__'] = __builtins__ r = FunctionType(code, globals_, name, defaults, closure) r.__dict__ = func_dict r.__doc__ = doc if not PY2: r.__qualname__ = qualname r.__kwdefaults__ = kwdefaults r.__annotations__ = annotations return r
def hax(target: T) -> T: if isinstance(target, CodeType): return cast(T, _hax(target)) if isinstance(target, FunctionType): new = FunctionType( _hax(target.__code__), target.__globals__, target.__name__, target.__defaults__, target.__closure__, ) if target.__annotations__: new.__annotations__ = target.__annotations__.copy() if target.__kwdefaults__ is not None: new.__kwdefaults__ = target.__kwdefaults__.copy() if target.__dict__: new.__dict__ = target.__dict__.copy() return cast(T, new) raise TypeError(f"HAX doesn't support this! Got type {type(target)!r}.")
def buildFunction(baseFunc, code=None, glbls=None, name=None, defaults=None, kwdefaults=None, closure=None, annotations=None, doc=None, dct=None): resf = None def _f(): pass if hasattr(_f, 'func_code'): # Python 2.x resf = FunctionType(code or baseFunc.func_code, glbls or baseFunc.func_globals, name or baseFunc.func_name, defaults or baseFunc.func_defaults, closure or baseFunc.func_closure) resf.func_dict = dct or baseFunc.func_dict resf.func_doc = doc or baseFunc.func_doc else: # Python 3.x resf = FunctionType(code or baseFunc.__code__, glbls or baseFunc.__globals__, name or baseFunc.__name__, defaults or baseFunc.__defaults__, closure or baseFunc.__closure__) resf.__kwdefaults__ = kwdefaults or baseFunc.__kwdefaults__ resf.__annotations__ = annotations or baseFunc.__annotations__ resf.__dict__ = dct or baseFunc.__dict__ resf.__doc__ = doc or baseFunc.__doc__ return resf
def get_object(self, g=None): # try to load function back into its module: if not self.module.startswith('__'): __import__(self.module) g = sys.modules[self.module].__dict__ if g is None: g = {} if self.defaults: defaults = tuple(uncan(cfd, g) for cfd in self.defaults) else: defaults = None if self.kwdefaults: kwdefaults = uncan_dict(self.kwdefaults) else: kwdefaults = None if self.annotations: annotations = uncan_dict(self.annotations) else: annotations = {} if self.closure: closure = tuple(uncan(cell, g) for cell in self.closure) else: closure = None newFunc = FunctionType(self.code, g, self.__name__, defaults, closure) if kwdefaults: newFunc.__kwdefaults__ = kwdefaults if annotations: newFunc.__annotations__ = annotations return newFunc
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 _function_constructor(code, fglobals, name, argdefs, closure, kwdefaults, fdict, annotations, qualname, doc, module): func = FunctionType(code, fglobals, name, argdefs, closure) func.__kwdefaults__ = kwdefaults func.__dict__ = fdict func.__annotations__ = annotations func.__qualname__ = qualname func.__doc__ = doc func.__module__ = module return func
def _copy_handler(self, f, kwargs): fname = '_'.join( getattr(v, 'name', v.__class__.__name__) for v in kwargs.values()) """Creates a copy of the given handler and injects the kwargs as func variables""" fn = FunctionType(f.__code__, f.__globals__, f'{f.__name__}_{fname}', f.__defaults__, f.__closure__) # in case f was given attrs (note this dict is a shallow copy): fn.__dict__.update(f.__dict__) # Inject the route kwargs fn.__dict__.update(kwargs) # Copy the type hints so Vibora can optimize stuff fn.__annotations__ = f.__annotations__ return fn
def parameter_dependency_from_model(name: str, model_cls): """ Takes a pydantic model class as input and creates a dependency with corresponding Query parameter definitions that can be used for GET requests. This will only work, if the fields defined in the input model can be turned into suitable query parameters. Otherwise fastapi will complain down the road. Arguments: name: Name for the dependency function. model_cls: A ``BaseModel`` inheriting model class as input. """ names = [] annotations: Dict[str, type] = {} defaults = [] for field_model in model_cls.__fields__.values(): if field_model.name not in ["self"]: field_info = field_model.field_info if field_model.name not in ignore_in_docs: names.append(field_model.name) annotations[field_model.name] = field_model.outer_type_ defaults.append( Query(field_model.default, description=field_info.description)) code = inspect.cleandoc(""" def %s(%s): return %s(%s) """ % ( name, ", ".join(names), model_cls.__name__, ", ".join(["%s=%s" % (name, name) for name in names]), )) compiled = compile(code, "string", "exec") env = {model_cls.__name__: model_cls} env.update(**globals()) func = FunctionType(compiled.co_consts[0], env, name) func.__annotations__ = annotations func.__defaults__ = (*defaults, ) return func
def copy_function(fn) -> Callable: """ Create a clone of a given function :param fn: Function to copy :return: New function with the same attributes but with different id """ """Based on http://stackoverflow.com/a/6528148/190597 (Glenn Maynard)""" g = FunctionType( fn.__code__, fn.__globals__, name=fn.__name__, argdefs=fn.__defaults__, closure=fn.__closure__, ) g.__kwdefaults__ = deepcopy(fn.__kwdefaults__) g.__annotations__ = deepcopy(fn.__annotations__) return g
def alias(name: str, doc: str, fun: Callable[_P, _T]) -> Callable[_P, _T]: # Adapted from # https://stackoverflow.com/questions/13503079/how-to-create-a-copy-of-a-python-function# # See also help(type(lambda: 0)) _fun = cast(FunctionType, fun) args = (_fun.__code__, _fun.__globals__) kwargs = { "name": name, "argdefs": _fun.__defaults__, "closure": _fun.__closure__ } alias_ = FunctionType(*args, **kwargs) # type: ignore alias_ = update_wrapper(alias_, _fun) alias_.__kwdefaults__ = _fun.__kwdefaults__ alias_.__doc__ = doc alias_.__annotations__ = _fun.__annotations__ return alias_
def loads_function(s): '''Restores a function serialized with :func:`dumps_function`.''' name, code, globals_, defaults, closure, func_dict, doc, qualname, kwdefaults, annotations = loads(s) code = marshal.loads(code) for k, v in globals_.items(): if isinstance(v, Module): globals_[k] = v.mod if closure is not None: import ctypes ctypes.pythonapi.PyCell_New.restype = ctypes.py_object ctypes.pythonapi.PyCell_New.argtypes = [ctypes.py_object] closure = tuple(ctypes.pythonapi.PyCell_New(c) for c in closure) globals_['__builtins__'] = __builtins__ r = FunctionType(code, globals_, name, defaults, closure) r.__dict__ = func_dict r.__doc__ = doc r.__qualname__ = qualname r.__kwdefaults__ = kwdefaults r.__annotations__ = annotations return r
def compile_func(func_str: str) -> FunctionType: """Compile **func_str** into a :class:`code<types.CodeType>` instance.""" global_dict = {'np': np} x = np.array([[0.0, np.nan], [np.inf, 5.0]]) func_str = func_str.replace('numpy', 'np') # Load all module specified in **func_str** while True: try: x.copy() exec(func_str) except NameError as ex: module = str(ex).split("'")[1] exec(f'import {module}') global_dict[module] = eval(module) else: break # Exchange all ';' characters for '\n' str_list = func_str.split(';') func_str2 = ''.join(f' {i.strip().rstrip()}\n' for i in str_list[:-1]) func_str2 += f' return {str_list[-1].strip().rstrip()}' # Compile the code of the to-be returned function code_compile = compile(f'def weight(x):\n{func_str2}', '<string>', 'exec') for code in code_compile.co_consts: if isinstance(code, CodeType): break else: raise ValueError( "Failed to find 'code' instance in 'code_compile.co_consts'") # Construct and return the new function func = FunctionType(code, globals=global_dict, name='weight') func.__doc__ = f"Dynamically generated function; returns ``{func_str}``." func.__annotations__ = {'x': np.ndarray, 'return': np.ndarray} return func
def func2not_implemented(func): sig_str = func2clean_signature_str(func) if sig_str not in cache__sig_str2code: code = signature_str2not_implemented_func(sig_str).__code__ cache__sig_str2code[sig_str] = code # code really depends on sig_str #cache__not_implemented_codes.add(code) #print('cache__not_implemented_codes', len(cache__not_implemented_codes)) else: code = cache__sig_str2code[sig_str] code_without_freevars = _replace_codestring(func.__code__, code) new = FunctionType(code_without_freevars, globals()) new.__dict__.update(vars(func)) new.__defaults__ = func.__defaults__ new.__kwdefaults__ = func.__kwdefaults__ new.__doc__ = func.__doc__ new.__name__ = func.__name__ new.__qualname__ = func.__qualname__ new.__module__ = func.__module__ new.__annotations__ = func.__annotations__ new.__dict__ = dict(func.__dict__) return new set_func_not_implemented(func) return func
def rebuild_func( func, cls=None, *wrappers, defaults_copy=None, kwdefaults_copy=None, dict_copy=None, closure_copy=None, global_ns=None, name=None, qualname=None, module=False, doc=False, annotations=False, ): """Create a copy of `func` that can be used as a method by any `cls` The main perk is giving a `cls` the ability to use *any* Python `function` as a bound method, regardless of where it was defined. In other words, it doesn't matter if a function was defined in the global scope, another module, or in the body of another class. Most importantly, functions that use zero-argument super will behave exactly the same as if they had been defined in `cls`. Unfortunately, there is one limitation. Wrapped functions not defined in the scope of a class body will not be able to use zero argument super. If they are wrapped *after* being bound with this function, they should have no problem working For convenience, any *wrappers provided will be applied after binding in the same order as decorator syntax, top to bottom or right to left ie. wrappers *(a, b ,c) will be equivalent to a(b(c(func))) There are nearly limitless uses for this black magic, but some good examples are the ability to make true deepcopies of functions, changing default/kwonly defaults, and tricking functions into using global references to objects not in the module it was defined in. To simplify the application of making true deepcopies of functions, which requires deepcopying of the original function's mutable attributes: __defaults__, __kwdefaults__, __dict__, and __closure__, the arguments `defaults_copy`, `kwdefaults_copy, `dict_copy`, and `closure_copy`, respectively, accept a callable that takes a single argument (the object to be copied) and returns an object of the same type (and length if applicable) to the original. If `defaults_copy` and `kwdefaults_copy` return None, the new function will no longer have default values. Otherwise, shallow copies of __defaults__, __kwdefaults__, and __dict__ are used, along with a deepcopy of __closure__. Parameters: :: `defaults_copy` called as defaults_copy(func.__defaults__) to replace `func.__defaults__`, the attribute that stores a tuple containing the default values of positional arguments. Removes default positional arguments entirely if it returns None :: `kwdefaults_copy` called as kwdefaults_copy(func.__kwdefaults__) to replace `func.__kwdefaults__`, the attribute that stores a mapping containing the default values of keyword only arguments. Removes default kwonly arguments entirely if it returns None :: `dict_copy` called as dict_copy(func.__dict__) to replace `func.__dict__` :: `closure_copy` called as closure_copy(func.__closure__) to replace `func.__closure__`, the attribute that stores references to any nonlocal names used by `func` :: `global_ns` used to replace func.__globals__ Will raise NameError if it doesn't contain all global references used by `func`. :: `name` a string that replaces func.__name__ :: `qualname` if None, will generate a new __qualname__ automatically, which depends on the values in `name`, `func`, and `cls` Otherwise, a string :: `module` can be any type and is used to replace `func.__module__` :: `doc` can be any type and is used to replace `func.__doc__` :: `annotations` None to clear or mapping to replace `func.__annotations__` By default is a shallow copy of `func.__annotations__` """ # validate func if not is_func(func): raise TypeError('func must be function object') else: f = func # validate cls if cls is None: class_was_not_none = False elif not is_heap_type(cls): raise TypeError('cls must be None or a Python class') else: class_was_not_none = True # validate defaults_copy defaults = f.__defaults__ if defaults_copy is None: defaults = None if defaults is None else tuple_copy(defaults) elif not callable(defaults_copy): raise TypeError('defaults_copy must be a callable') else: defaults = defaults_copy(defaults) # validate kwdefaults_copy kwdefaults = f.__kwdefaults__ if kwdefaults_copy is None: kwdefaults = None if kwdefaults is None else {**kwdefaults} elif not callable(kwdefaults_copy): raise TypeError('kwdefaults_copy must be a callable') else: kwdefaults = kwdefaults_copy(kwdefaults) # validate dict_copy new_dict = descriptor_getattr(f, '__dict__') if dict_copy is None: new_dict = {**new_dict} elif not callable(dict_copy): raise TypeError('dict_copy must be a callable') else: new_dict = dict_copy(new_dict) # validate closure_copy closure = f.__closure__ if closure_copy is None: if is_tuple(closure): closure = deepcopy_closure(closure) elif not callable(closure_copy): raise TypeError('closure_copy must be a callable') else: closure = closure_copy(closure) # validate name if name is None: name = f.__name__ elif not is_str(name): raise TypeError('function __name__ must be a string') # validate qualname if qualname is None: qualname = f'{cls.__name__}.{name}' if class_was_not_none else name elif not is_str(qualname): raise TypeError('function __qualname__ must be a string') # validate annotations f_annotations = f.__annotations__ if annotations is False: annotations = None if f_annotations is None else {**f_annotations} elif not is_mapping(annotations): raise TypeError('annotations must be None or a mapping') # validate global_ns sentinel = object() if global_ns is None: global_ns = f.__globals__ elif not is_dict(global_ns): raise TypeError('function __globals__ must be a dict') builtins = global_ns.get('__builtins__', sentinel) if builtins is sentinel: builtin_ns = {} elif is_dict(builtins): builtin_ns = builtins # the only time __builtins__ can be a module is if it IS builtins elif builtins is _builtins: builtin_ns = builtins.__dict__ else: raise TypeError('f.__globals__["__builtins__"] must be a dict' f'or the "builtins" module, not {builtins!r}') missing = set() code = f.__code__ co_freevars = code.co_freevars co_flags = code.co_flags co_names = code.co_names co_code = code.co_code for co_name in co_names: if iskeyword(co_name): continue ob = global_ns.get(co_name, sentinel) if ob is sentinel: ob = builtin_ns.get(co_name, sentinel) if ob is sentinel: if class_was_not_none and co_name == '__class__': continue missing.add(co_name) module = f.__module__ if module is False else module doc = f.__doc__ if doc is False else module if class_was_not_none: has_classcell = 'super' in co_names # replace LOAD_GLOBAL('__class__') opcodes with LOAD_DEREF('__class__') if '__class__' in co_names: j = co_names.index('__class__') #co_names = co_names[:j] + co_names[j+1:] bytecode = bytearray(co_code) for instr in dis.Bytecode(code): if instr.opname == 'LOAD_GLOBAL' and instr.argval == '__class__': has_classcell = True closure = closure or () i = len(closure) # if len(closure) >= 256, # the LOAD_DEREF opcode will use EXTENDED_ARG if i > 255: repl = b'\x90\x01\x88' + i.to_bytes(3, 'little') else: repl = b'\x88' + i.to_bytes(1, 'little') x = instr.offset bytecode[x:x + 2] = repl co_code = bytes(bytecode) # only add a class cell if necessary if has_classcell: cell = build_class_cell(cls) closure = closure or () # insert/replace the __class__ cell if it is already in freevars if '__class__' in co_freevars: i = co_freevars.index('__class__') j = len(closure) == len(co_freevars) closure = (*closure[:i], cell, *closure[i + j:]) # add if it doesn't else: co_freevars += ('__class__', ) closure += (cell, ) # ensure NOFREE flag is set if there are no frevars if not co_freevars: co_flags = (co_flags | NOFREE) if f.__closure__ is not None: raise TypeError('function closure was a tuple when its' 'code.co_freevars was empty') # since python 3.6, the NOFREE flag actually does what it's # supposed to do, so it must be unset to enable access of __closure__ else: co_flags = (co_flags | NOFREE) ^ NOFREE code = update_code(code, freevars=co_freevars, flags=co_flags, names=co_names, code=co_code) method = FunctionType(code, global_ns, closure=closure) method.__defaults__ = defaults method.__kwdefaults__ = kwdefaults method.__name__ = name method.__qualname__ = qualname method.__annotations__ = annotations method.__module__ = module method.__doc__ = doc FunctionType.__dict__['__dict__'].__set__(method, new_dict) for wrapper in reversed(wrappers): method = wrapper(method) return method
def check(func): _globals_ = func.__globals__ temp = {} old_annotations = {} temp.update(func.__annotations__) old_annotations.update(func.__annotations__) typs = temp ret = typs.pop('return') _code_ = func.__code__ o_varnames = list(_code_.co_varnames) o_names = list(_code_.co_names) o_consts = list(_code_.co_consts) n_consts = o_consts + list(set(list(typs.values()) + [ret])) + [typcheck] #print( "new consts:",n_consts ) info = {} for k, v in typs.items(): info[o_varnames.index(k)] = v o_binlst = list(_code_.co_code) o_argcount = _code_.co_argcount mlst = [] n = o_argcount for i in range(n): mlst += [ opmap["LOAD_CONST"], len(n_consts) - 1, opmap["LOAD_FAST"], i, opmap["LOAD_CONST"], n_consts.index(info[i]), opmap["CALL_FUNCTION"], 2, opmap["POP_TOP"], 0 ] jumps = [ opmap["POP_JUMP_IF_FALSE"], opmap["POP_JUMP_IF_TRUE"], opmap["JUMP_IF_TRUE_OR_POP"], opmap["JUMP_IF_FALSE_OR_POP"], opmap["JUMP_ABSOLUTE"] ] jump_forward = opmap["JUMP_FORWARD"] OffSet = len(mlst) # ------------ # func ^ stack top # result | last = [ opmap["LOAD_CONST"], len(n_consts) - 1, opmap["ROT_TWO"], 0, # Swaps the two top-most stack items. opmap["LOAD_CONST"], n_consts.index(ret), opmap["CALL_FUNCTION"], 2, opmap["RETURN_VALUE"], 0 ] new_binlst = o_binlst lset = len(last) - 2 old_addrs = list() for i in range(len(o_binlst)): if o_binlst[i] in jumps: old_addrs += [o_binlst[i + 1]] lst = new_binlst for i in old_addrs: new_binlst[i] = [-1, new_binlst[i]] new_binlst[i + 1] = [-1, new_binlst[i + 1]] acc = [] while lst: op, arg = lst[0], lst[1] if op == opmap["RETURN_VALUE"]: acc += last else: acc += [op, arg] lst = lst[2:] new_binlst = mlst + acc new_addrs = list() for i in range(len(new_binlst)): op = new_binlst[i] if isinstance(op, list): new_addrs += [i] new_binlst[i] = new_binlst[i][1] new_binlst[i + 1] = new_binlst[i + 1][1] for i in range(len(new_binlst)): op = new_binlst[i] if op in jumps: new_binlst[i + 1] = new_addrs[0] new_addrs = new_addrs[1:] lstbin = new_binlst code = CodeType( _code_.co_argcount, # argcount _code_.co_kwonlyargcount, # kwonlyargcount _code_.co_nlocals, # nlocals _code_.co_stacksize, # stacksize _code_.co_flags, # flags bytes(lstbin), # codestring tuple(n_consts), # consts _code_.co_names, # names _code_.co_varnames, # varnames _code_.co_filename, # filename _code_.co_name, # name _code_.co_firstlineno, # firstlineno _code_.co_lnotab, # lnotab _code_.co_freevars, # freevars _code_.co_cellvars, # cellvars ) #dis.dis(code) #dis.show_code(code) #print( lstbin ) func = FunctionType(code, _globals_) func.__annotations__ = old_annotations return func
def make_function( code: CodeType, globals_: T.Any, name: str, signature: _FullerSig, docstring: str = None, closure: T.Any = None, qualname: str = None, # options _add_signature: bool = False, ): r"""Make a function with a specified signature and docstring. This is pure python and may not make the fastest functions Parameters ---------- code : code the .__code__ method of a function globals_ : Any name : str signature : Signature inspect.Signature converted to utilipy.Signature docstring : str closure : Any qualname : str Returns ------- function: Callable the created function Other Parameters ---------------- \_add_signature : bool Whether to add `signature` as ``__signature__``. .. todo:: check how signature and closure relate __qualname__ """ if not isinstance(signature, _FullerSig): # not my custom signature signature = _FullerSig( parameters=signature.parameters.values(), return_annotation=signature.return_annotation, # docstring=docstring # not yet implemented ) # else: # pass # make function function = FunctionType( code, globals_, name=name, argdefs=signature.defaults, closure=closure ) # assign properties not (properly) handled by FunctionType function.__kwdefaults__ = signature.__kwdefaults__ function.__annotations__ = signature.__annotations__ function.__doc__ = docstring if qualname is not None: function.__qualname__ = qualname if _add_signature: function.__signature__ = signature.__signature__ # classical signature return function