Python 的 types.FunctionType 如何创建动态函数?

How does Python's types.FunctionType create dynamic Functions?

我正在努力加强我的 Python 技能,我遇到了正在使用 types.FunctionType 的 Open-Source code for Saltstack,但我不明白这是怎么回事。

salt.cloud.clouds.cloudstack.py

函数 create() 有以下代码:

kwargs = {
    'name': vm_['name'],
    'image': get_image(conn, vm_),
    'size': get_size(conn, vm_),
    'location': get_location(conn, vm_),
}

函数 get_image 和 get_size 被传递给函数 'namespaced_function' 如下:

get_size = namespaced_function(get_size, globals())
get_image = namespaced_function(get_image, globals())

salt.utils.functools.py

具有命名空间函数

def namespaced_function(function, global_dict, defaults=None, preserve_context=False):
    '''
    Redefine (clone) a function under a different globals() namespace scope
        preserve_context:
            Allow keeping the context taken from orignal namespace,
            and extend it with globals() taken from
            new targetted namespace.
    '''
    if defaults is None:
        defaults = function.__defaults__

    if preserve_context:
        _global_dict = function.__globals__.copy()
        _global_dict.update(global_dict)
        global_dict = _global_dict
    new_namespaced_function = types.FunctionType(
        function.__code__,
        global_dict,
        name=function.__name__,
        argdefs=defaults,
        closure=function.__closure__
    )
    new_namespaced_function.__dict__.update(function.__dict__)
    return new_namespaced_function

我可以看到他们正在动态创建一个函数 get_image,但我不明白这样做的好处。为什么不直接创建函数?

由于 namespaced_function() 将(旧)函数 get_image() 作为参数,而 returns 将(新)get_image() 函数作为参数,更恰当地说它正在修改函数,而不是创建它。当然,它正在创建一个函数并返回它,但它与输入函数非常相似。 namespaced_function() 有点像装饰器,但装饰器通常只是将整个输入函数包装在另一个调用原始函数的函数中,而不是 实际上 创建原始函数的修改版本.原来的get_image()定义在libcloudfuncs.py.

所以问题就变成了,"How does namespaced_function() modify the input function?"。如果您查看 types.FunctionType() 作为其参数获取的内容,您会发现大多数值都是直接从原始函数复制过来的。唯一没有直接复制的参数是函数的全局变量。换句话说,namespaced_function() 正在创建一个新函数,它在各个方面都与输入函数相同,除了当函数引用全局变量时,它会在不同的地方查找它们。

因此,他们正在创建一个新版本的 get_image(),它也可以访问当前模块的全局变量。他们为什么要那样做?好吧,要么覆盖一些全局变量,要么提供原始模块中根本不存在的变量(在这种情况下,原始函数在修改之前会被故意破坏)。但我不能 真的 回答 "Why?" 除了总结地说他们可能认为它比其他选择更容易。

那么有哪些选择呢?好吧,全局变量通常不受欢迎 - 当它们不是常量时 - 因为,好吧,您可能想要更改它们。他们本可以使用额外的参数而不是全局变量,但可能不想在他们的大多数函数使用它们时一直传递相同的参数。不过,您也可以注入参数,就像它们注入全局变量一样——而且它也不那么骇人听闻!那么他们为什么不这样做呢?好吧,我不得不再次猜测,但它们可能有不止一个全局变量,它们是 changing/providing.

自动提供参数很容易:

def original(auto1, arg1, arg2, auto2):
    print(auto1, auto2, arg1, arg2)

injected = functools.partial(original, 'auto1', auto2='auto2')

injected(1, 2) # is equal to original('auto1', 1, 2, 'auto2')

自动提供大量参数很快就会变得乏味。

当然,您可以让所有函数都有一个名为 eg 的参数。 globals 作为第一个参数,并使用 injected = functools.partial(original, globals())。但是在函数内部,每当你需要引用这样一个变量时,你需要说 globals['variable'] 而不是 variable.

所以,总而言之,它可能有点老套,但作者可能已经判断 "a bit hacky" 仍然比更冗长的替代方案好很多。