天天看点

python笔记76-types.FunctionType 动态创建函数

前言

types.FunctionType 创建函数有2种方式:

  1. 从已有函数的基础上,创建一个新函数
  2. 从一个compile 构建的函数对象上,创建一个新函数

FunctionType 使用

FunctionType 可以用于判断一个对象是不是函数

from types import FunctionType, MethodType


def func():
    print("hello")


class Demo:
    x = 1

    def fun(self):
        print(self.x)

    @staticmethod
    def fun2():
        print("f2")


print(type(func))  # <class 'function'>
x = Demo()
print(type(x.fun))  # <class 'method'>
print(type(x.fun2))  # <class 'function'>

# 判断是函数还是方法
print(isinstance(func, FunctionType))  # True
print(isinstance(x.fun, MethodType))  # True
print(isinstance(x.fun2, FunctionType))  # True      

创建新函数

从已有函数的基础上,创建一个新函数

5个参数

  • code是函数体的code对象
  • globals就是当前环境下的globals变量
  • name就是函数本身的名字
  • argdefs保存了函数的默认参数,这里可以注意到,code里只包含函数执行的逻辑,而默认参数则是在函数声明里
  • closure是闭包的变量,换句话说是既不在locals里,也不在globals的变量
import types


def foobar():
    return "foobar"


dynamic_fun = types.FunctionType(foobar.__code__, {})
print(dynamic_fun())  # foobar      

配合compile函数 创建函数

使用示例

import types

f = """
def foobar(): 
    return "foobar"
"""

# 字符串编译成code
module_code = compile(f, '', 'exec')
# 从编译的code obj 取出CodeType 类型
function_code = module_code.co_consts[0]
foobar = types.FunctionType(function_code, {})
print(foobar())      

FunctionType 需传一个CodeType 类型,可以从compile() 函数编译后的code取出编译后的code 类型

python笔记76-types.FunctionType 动态创建函数

动态创建函数

如果通过一个函数动态创建更多的函数,可以参考这篇​​https://zhuanlan.zhihu.com/p/386276353​​

import sys
import types
from typing import Any, Callable, Mapping, Sequence
from inspect import Parameter, Signature


def create_function_from_parameters(
        func: Callable[[Mapping[str, Any]], Any],
        parameters: Sequence[Parameter],
        documentation=None,
        func_name=None,
        func_filename=None):
    new_signature = Signature(parameters)  # Checks the parameter consistency

    def pass_locals():
        return dict_func(locals())  # noqa: F821 TODO

    code = pass_locals.__code__
    mod_co_argcount = len(parameters)
    mod_co_nlocals = len(parameters)
    mod_co_varnames = tuple(param.name for param in parameters)
    mod_co_name = func_name or code.co_name
    if func_filename:
        mod_co_filename = func_filename
        mod_co_firstlineno = 1
    else:
        mod_co_filename = code.co_filename
        mod_co_firstlineno = code.co_firstlineno

    if sys.version_info >= (3, 8):
        modified_code = code.replace(
            co_argcount=mod_co_argcount,
            co_nlocals=mod_co_nlocals,
            co_varnames=mod_co_varnames,
            co_filename=mod_co_filename,
            co_name=mod_co_name,
            co_firstlineno=mod_co_firstlineno,
        )
    else:
        modified_code = types.CodeType(
            mod_co_argcount,
            code.co_kwonlyargcount,
            mod_co_nlocals,
            code.co_stacksize,
            code.co_flags,
            code.co_code,
            code.co_consts,
            code.co_names,
            mod_co_varnames,
            mod_co_filename,
            mod_co_name,
            mod_co_firstlineno,
            code.co_lnotab
        )

    default_arg_values = tuple(p.default for p in parameters if p.default != Parameter.empty) #!argdefs "starts from the right"/"is right-aligned"
    modified_func = types.FunctionType(modified_code, {'dict_func': func, 'locals': locals}, name=func_name, argdefs=default_arg_values)
    modified_func.__doc__ = documentation
    modified_func.__signature__ = new_signature
    return modified_func


def foo(arg):
    print(arg)
    return "x"


f = create_function_from_parameters(
    func=foo,
    parameters=[Parameter('x', Parameter.POSITIONAL_OR_KEYWORD)],
    documentation="some doc",
    func_name="bar",
    func_filename="main.py",
)

print(f('xxx'))      

等价于

def bar(a):
    """some doc"""
    foo({"x":a})
f = bar