在 Python 中更改函数的范围
Posted
技术标签:
【中文标题】在 Python 中更改函数的范围【英文标题】:Change the scope of a function in Python 【发布时间】:2020-10-14 00:34:06 【问题描述】:我的目标是将函数的范围更改为字典,而不是定义它的位置,以便函数看到字典中的变量。
我发现我能够做到以下几点:
my_dict = 'x': 1, 'y': 2
def add_all():
x + y + z
# reference the functions in the dictionary
my_dict.update('add_all': add_all)
# append my_dict to __global__ of the function
my_dict['add_all'].__globals__.update(my_dict)
z = 3
my_dict['add_all']() # sees x, y and z
# 6
这可行,现在我尝试创建另一个函数来更改封闭范围内的变量。
def update_x_y(x, y):
# Have to explicitly refer to my_dict here
my_dict.update('x': x, 'y': y)
# Must update the __globals__ of add_all() again
add_all.__globals__.update(my_dict)
add_all()
my_dict.update('update_x_y': update_x_y)
my_dict['update_x_y'].__globals__.update(my_dict)
my_dict['update_x_y'](10, 20)
# 33
这也有效,但非常不优雅和危险。
问题:
看起来__globals__
是一个函数将在其中求值的字典;我对__globals__.update()
所做的只是给它一些新值,所以每次my_dict
发生变化时,我都必须再次更新。
my_dict
代替__globals__
,尽管是readonly
?
在update_x_y()
函数中,我必须明确引用在全局范围内定义的my_dict
。
nonlocal
不能使用,因为封闭范围必须是闭包
【问题讨论】:
你能提供更多关于你的目标的背景信息吗?这闻起来像 XY 问题 嗨@Chris_Rands,我只是想使用my_dict
作为两个函数的封闭范围来评估。
【参考方案1】:
我们可以使用变量注入类来清理代码
灵感来自
How to inject variable into scope with a decorator Using classes as decorators代码
class Inject_Variables:
""" Injects Named Variables into function scope """
def __init__(self, **kwargs):
if kwargs:
self.kwargs = kwargs
def __call__(self, f):
"""
__call__() is only called
once, as part of creation of the wrapped function! You can only give
it a single argument, which is the function object.
"""
@wraps(f)
def wrapped_f(*args, **kwargs):
f_globals = f.__globals__
saved_values = f_globals.copy() # shallow copy
# Add Object creation context
if self.kwargs:
f_globals.update(self.kwargs)
# Add Function call context
if kwargs:
f_globals.update(kwargs)
try:
result = f()
finally:
f_glabals = saved_values # Undo changes
return result
return wrapped_f
用例
def add_all():
return x + y + z
测试 1
my_dict = 'x': 1, 'y': 2
z = 3
# Create add that will use my_dict and z in global scope
scoped_add = Inject_Variables(**my_dict)(add_all)
print('Using global z: ', scoped_add()) # use my_dict values
# Output: Using global z: 6
测试 2
print('Using calling parameter z: ', scoped_add(z=0))
# Output: Using calling parameter z: 3
测试 3
# Update dictionary
my_dict = 'x': 2, 'y': 3, 'z': 0
scoped_add = Inject_Variables(**my_dict)(add_all) # new scoped add
print('Using dictionary z: ', scoped_add())
# Ouput: Using updated dictionary: 5
测试 4
# Using name parameters
# Define new scope using named parameters
z = 300
scoped_add = Inject_Variables(x = 100, y = 200)(add_all)
print('Using named parameters: ', scoped_add())
# Output: Using named parameters: 600
测试 5
z = 3
@Inject_Variables(x=1, y = 2)
def test_all():
return x + y + z
print('Using in decorator: ', test_all())
# Output: Using in decorator: 6
【讨论】:
这看起来很棒。但是,我想知道这个设置是否可以让函数也操作字典中的数据,或者它只是单向流动? @SiqiZhang--似乎你可以操纵__init__
和__call__
中的数据。取决于您需要操作的动态程度。您也可以轻松地使用不同的数据创建不同的函数(即 scoped_add)。以上是关于在 Python 中更改函数的范围的主要内容,如果未能解决你的问题,请参考以下文章
为啥 Python 3.8.0 允许在不使用“非本地”变量的情况下从封闭函数范围更改可变类型?