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
中需要a
和b
,但我在其他任何地方都不需要它们。特别是,可以写a = 5
,这将改变f
的行为。我应该如何使a
和b
对外部不可见?
其他语言让我大致可以写出如下代码:
let f =
a = 2
b = 3
lambda x: x * a + b
我想要什么:
f
必须按预期工作并具有相同的签名
a
和 b
只能计算一次
a
和 b
不得存在于f
之外的范围内
分配a = ...
和b = ...
不影响f
执行此操作的最简洁方法。例如。以下解决方案正式有效,但它引入了g
,然后将其删除,这是我不喜欢的(例如,存在覆盖现有g
的风险,我认为它很丑):
def g():
a = 2
b = 3
return lambda x: x * a + b
f = g()
del g
【问题讨论】:
【参考方案1】:一种方法是简单地使用一个类。这允许您将a
和b
放在类的范围内,而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 中提供的答案,但仅限于上述特定问题。您可以使用装饰器更新函数可用的全局变量,同时还可以将 a
和 b
存储在闭包中。
带闭包的装饰器
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
这会保留函数签名并防止 a
和 b
被更改
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 - 将变量范围绑定到闭包的主要内容,如果未能解决你的问题,请参考以下文章