Python - 将变量范围绑定到闭包

Posted

技术标签:

【中文标题】Python - 将变量范围绑定到闭包【英文标题】:Python - bound variable scope to closure 【发布时间】: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

【问题讨论】:

【参考方案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

【讨论】:

以上是关于Python - 将变量范围绑定到闭包的主要内容,如果未能解决你的问题,请参考以下文章

python中的闭包

流畅的python第七章函数装饰器和闭包学习记录

python中的闭包

闭包之我见

python中对 函数 闭包 的理解

python学习