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

时间:2018-02-05 18:33:23

标签: python salt-stack

我正在努力增强我的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,但我不明白这样做的好处。为什么不创建函数?

1 个答案:

答案 0 :(得分:3)

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

因此问题变成:“ namespaced_function()如何修改输入功能?”。如果查看types.FunctionType()作为参数,您会看到大多数值都是直接从原始函数中复制过来的。唯一不直接复制的参数是函数的全局变量。换句话说,namespaced_function()正在创建一个新函数,该函数在所有方面都与输入函数相同,不同之处在于,当该函数引用全局变量时,它将在不同的位置查找它们。

因此,他们正在创建get_image()的新版本,该版本也可以访问当前模块的全局变量。他们为什么要这样做?好吧,要么重写某些全局变量,要么提供原始模块中根本不存在的变量(在这种情况下,修改前会故意破坏原始函数)。但是我真的不能 回答“为什么?”除了概括地说他们可能认为它比其他选择容易之外。

那么什么是 ?好吧,当全局变量不是恒定的时,它们常常会被皱眉,因为好吧,您可能想要更改它们。他们本可以使用额外的参数而不是全局变量,但是当大多数函数使用它们时,它们可能不想一直传递相同的参数。不过,您也可以注入参数,就像它们注入了全局变量一样,而且也不太hacky!那他们为什么不这样做呢?再说一次,我有点不得不猜测,但是他们可能正在更改/提供多个全局变量。

自动提供参数很容易:

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

因此,总而言之,它可能有点笨拙,但作者可能已经判断出“有点笨拙”仍然比更冗长的替代方案好很多。