【问题标题】:Python - bound variable scope to closurePython - 将变量范围绑定到闭包
【发布时间】:2021-04-16 06:49:15
【问题描述】:

我有一些使用外部变量的函数。一个(基本上)简化的例子:

a = 2
b = 3

def f(x):
    return x * a + b

虽然我在f 中需要ab,但我在其他任何地方都不需要它们。特别是,可以写a = 5,这将改变f 的行为。我应该如何使ab 对外部不可见?

其他语言让我大致可以写出如下代码:

let f = 
   a = 2
   b = 3
   lambda x: x * a + b

我想要什么:

  • f 必须按预期工作并具有相同的签名
  • ab 只能计算一次
  • ab 不得存在于f 之外的范围内
  • 分配a = ...b = ... 不影响f
  • 执行此操作的最简洁方法。例如。以下解决方案正式有效,但它引入了g,然后将其删除,这是我不喜欢的(例如,存在覆盖现有g 的风险,我认为它很丑):
def g():
    a = 2
    b = 3
    return lambda x: x * a + b

f = g()
del g

【问题讨论】:

    标签: python scope closures


    【解决方案1】:

    一种方法是简单地使用一个类。这允许您将ab 放在类的范围内,而f 仍然可以访问它们。

    自定义类

    class F:
        def __init__(self):
            self.a = 2
            self.b = 3
        
        def __call__(self, x):
            return x * self.a + self.b
    
    f = F()
    f(1)
    # returns:
    5
    

    如果您不喜欢调用类构造函数,您可以覆盖__new__ 实质上创建一个带有内部存储变量的可调用对象。虽然这是一个反模式,但不是很pythonic。

    自定义可调用

    class f:
        a = 2
        b = 3
    
        def __new__(cls, x):
            return x * cls.a + cls.b
    
    f(1)
    # returns:
    5
    

    此方法基于this thread 中提供的答案,但仅限于上述特定问题。您可以使用装饰器更新函数可用的全局变量,同时还可以将 ab 存储在闭包中。

    带闭包的装饰器

    from functools import wraps
    
    def dec_ab(fn):
        a = 2
        b = 3
        @wraps(fn)
        def wrapper(*args, **kwargs):
            # get global scope
            global_scope = f.__globals__
    
            # copy current values of variables
            var_list = ['a', 'b']
            current_vars = {}
            for var in var_list:
                if var in global_scope:
                    current_vars[var] = global_scope.get(var)
    
            # update global scope
            global_scope.update({'a': a, 'b': b})
            try:
                out = fn(*args, **kwargs)
            finally:
                # undo the changes to the global scope
                for var in var_list:
                    global_scope.pop(var)
                global_scope.update(current_vars)
    
            return out
        return wrapper
    
    @dec_ab
    def f(x):
        """hello world"""
        return x * a + b
    

    这会保留函数签名并防止 ab 被更改

    f(1)
    # returns: 
    5
    
    a
    # raises:
    NameError: name 'a' is not defined
    

    【讨论】:

    • 我认为这与问题中的g 的解决方案大致相同,不是吗?
    • 谢谢。我想我理解这个解决方案,它完全符合我的要求,但它不会误导读者吗?我的意思是,当人们看到__new__ 时,他们会期待一个创建类实例的方法。
    • 哦,当然。这绝对是一种反模式。
    • 我认为第二种解决方案的合理折衷方案是@staticmethod def apply(x):,然后是f.apply(1)(当然,它会更改签名)。
    • 又增加了一种方法。这是我能找到的最接近您正在寻找的东西。
    【解决方案2】:

    您可以使用默认参数来完成此操作。默认参数仅在创建闭包时计算一次(这就是为什么如果您将可变对象作为默认参数,则在调用之间保留状态)。

    def f(x, a=2, b=3):
        return x * a + b
    

    【讨论】:

      猜你喜欢
      • 2012-06-22
      • 1970-01-01
      • 1970-01-01
      • 2018-01-28
      • 1970-01-01
      • 2015-02-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多