在 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 允许在不使用“非本地”变量的情况下从封闭函数范围更改可变类型?

Python入门之函数结构——第3关:函数的使用范围:Python作用域

VueJs 上 $route 监视函数中的组件范围不正确

python函数2_闭包和装饰器

global用法