我正在努力增强我的Python技能,我遇到了使用types.FunctionType的Saltstack的开源代码,我不明白发生了什么.
函数create()有以下代码:
kwargs = {
'name': vm_['name'],
'image': get_image(conn, vm_),
'size': get_size(conn, vm_),
'location': get_location(conn, vm_),
}
Run Code Online (Sandbox Code Playgroud)
函数get_image和get_size传递给函数'namespaced_function',如下所示:
get_size = namespaced_function(get_size, globals())
get_image = namespaced_function(get_image, globals())
Run Code Online (Sandbox Code Playgroud)
具有命名空间功能
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
Run Code Online (Sandbox Code Playgroud)
我可以看到他们正在动态创建一个函数get_image,但我不明白这样做的好处.为什么不创建这个功能呢?
由于namespaced_function()将(旧)函数get_image()作为参数并返回(新)get_image()函数,因此更容易说它是在修改函数,而不是创建函数。当然,它创建一个函数并返回它,但它与输入函数非常相似。namespaced_function()工作原理有点像装饰器,但装饰器通常只是将整个输入函数包装在另一个调用原始函数的函数中,而不是实际创建原始函数的修改版本。原始版本在libcloudfuncs.pyget_image()中定义。
那么问题就变成了“如何namespaced_function()修改输入功能?”。如果您查看types.FunctionType()作为其参数的内容,您会发现大多数值都是直接从原始函数复制的。唯一不直接复制的参数是函数的全局变量。换句话说,namespaced_function()就是创建一个新函数,该函数在各方面都与输入函数相同,只是当该函数引用全局变量时,它会在不同的位置查找它们。
因此,他们正在创建一个新版本,get_image()该版本也可以访问当前模块的全局变量。他们为什么要这么做?好吧,要么覆盖一些全局变量,要么提供原始模块中根本不存在的变量(在这种情况下,原始函数在修改之前会被故意破坏)。但我真的不能回答“为什么?” 除了简单地说他们可能认为它比其他选择更容易。
那么还有哪些替代方案呢?好吧,当全局变量不是常量时,人们常常不赞成它们,因为,好吧,您可能想要更改它们。他们本可以使用额外的参数而不是全局变量,但可能不想在大多数函数使用它们时继续传递相同的参数。不过,您也可以注入参数,就像它们注入全局变量一样 - 而且它也更简单!那么他们为什么不这样做呢?好吧,我又不得不猜测,但他们可能有多个正在更改/提供的全局变量。
自动提供参数很容易:
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')
Run Code Online (Sandbox Code Playgroud)
自动提供大量参数很快就会变得乏味。
当然,您可以让所有函数都有一个名为 例如 的参数。globals作为第一个参数,并使用injected = functools.partial(original, globals()). 但在函数内部,每当您需要引用这样的变量时,您需要说globals['variable']而不是仅仅说variable.
因此,总而言之,它可能有点 hacky,但作者可能认为“有点 hacky”仍然比更冗长的替代方案要好得多。