创建闭包的 Pythonic 方式
Pythonic way to create closures
我的代码存在于 GUI 之外的文件中,但生成了 GUI 调用的方法。例如,此文件包含如下所示的函数:
# old code
def fixDictionary(dictionary, key, new_value):
def fix():
dictionary[key] = new_value
return fix
在闭包中包装 dictionary
的一般方法工作正常,但这种方法会导致大量用于创建无参数函数的样板代码。我做了一个简单的装饰器来为我做这个,如下所示。
# new code
from functools import wraps
def strip_args(function):
def outer(*args, **kwargs):
@wraps(function)
def inner():
function(*args, **kwargs)
return inner
return outer
@strip_args
def fixDictionary(dictionary, key, new_value):
dictionary[key] = new_value
作为参考,此函数的用法类似于:
dictionary = {'key': 'old_value'}
fixer = fixDictionary(dictionary, 'key', 'new_value')
fixer()
print(dictionary) # {'key': 'new_value'}
然后,我的代码中还有一堆方法,如下所示:
# old code
def checkDictionary(dictionary):
errors = []
for key, value in dictionary.items():
if value == 'old_value':
error.append(fixDictionary(dictionary, key, 'new_value'))
return errors
如果不清楚,这些方法会检查对象是否有错误,然后 return GUI 可以调用的匿名函数以更正这些错误。但是,所有这些方法都会初始化一个空白容器,向其中添加项目,然后 return 它。为了去除所有这些函数中的重复代码,我写了另一个装饰器:
# new code
def init_and_return(**init_dict):
if len(init_dict) != 1:
raise ValueError('Exactly one "name=type" pair should be supplied')
_name, _type = init_dict.items()[0]
def outer(function):
@wraps(function)
def inner(*args, **kwargs):
_value = _type()
function.func_globals[_name] = _value
function(*args, **kwargs)
return _value
return inner
return outer
@init_and_return(errors=list)
def checkDictionary(dictionary):
for key, value in dictionary.items():
if value == 'old_value':
errors.append(fixDictionary(dictionary, key, 'new_value'))
现在,最终用法如下所示:
dictionary = {'key': 'old_value'}
errors = checkDictionary(dictionary) # [<function fixDictionary at 0x01806C30>]
errors[0]()
print(dictionary) # {'key': 'new_value'}
这也很好用,让我也避免为这些函数编写更多样板。我对上面的实现有两个问题:
- 是否有更 Pythonic 的方法来实现此功能?目的是消除每个函数中的所有样板代码,但是编写函数
strip_args
和 init_and_return
肯定会让大脑紧张。虽然这样的函数不必经常编写,但它们似乎与实际行为相去甚远。
- 第
function.func_globals[_name] = _value
行有异常行为;它允许从全局范围访问 errors
。这不是世界末日,因为每次调用函数时都会重置变量,但是无论如何我都可以设置局部变量吗?我已尝试将此行更改为 locals()[_name] = _value
,但作用域并未传递给函数。这种级别的元编程是否超出了 Python 的预期范围?
我想出了一个方法来解决我的第二个问题,方法是在 init_and_return
函数中实现一些簿记代码,检查它是否正在覆盖全局变量,然后在覆盖时恢复它(或者在覆盖时删除它)不是)。
def init_and_return(**init_dict):
# this could be extended to allow for more than one k-v argument
if len(init_dict) != 1:
raise ValueError('Exactly one "name=type" pair should be supplied')
_name, _type = init_dict.items()[0]
def outer(function):
@wraps(function)
def inner(*args, **kwargs):
# instantiate a new container
_value = _type()
# used to roll-back the original global variable
_backup, _check = None, False
# store original global variable (if it exists)
if _name in function.func_globals:
_backup = function.func_globals[_name]
_check = True
# add container to global scope
function.func_globals[_name] = _value
function(*args, **kwargs)
# roll-back if it existed beforehand, delete otherwise
if _check:
function.func_globals[_name] = _backup
else:
del function.func_globals[_name]
return _value
return inner
return outer
我的代码存在于 GUI 之外的文件中,但生成了 GUI 调用的方法。例如,此文件包含如下所示的函数:
# old code
def fixDictionary(dictionary, key, new_value):
def fix():
dictionary[key] = new_value
return fix
在闭包中包装 dictionary
的一般方法工作正常,但这种方法会导致大量用于创建无参数函数的样板代码。我做了一个简单的装饰器来为我做这个,如下所示。
# new code
from functools import wraps
def strip_args(function):
def outer(*args, **kwargs):
@wraps(function)
def inner():
function(*args, **kwargs)
return inner
return outer
@strip_args
def fixDictionary(dictionary, key, new_value):
dictionary[key] = new_value
作为参考,此函数的用法类似于:
dictionary = {'key': 'old_value'}
fixer = fixDictionary(dictionary, 'key', 'new_value')
fixer()
print(dictionary) # {'key': 'new_value'}
然后,我的代码中还有一堆方法,如下所示:
# old code
def checkDictionary(dictionary):
errors = []
for key, value in dictionary.items():
if value == 'old_value':
error.append(fixDictionary(dictionary, key, 'new_value'))
return errors
如果不清楚,这些方法会检查对象是否有错误,然后 return GUI 可以调用的匿名函数以更正这些错误。但是,所有这些方法都会初始化一个空白容器,向其中添加项目,然后 return 它。为了去除所有这些函数中的重复代码,我写了另一个装饰器:
# new code
def init_and_return(**init_dict):
if len(init_dict) != 1:
raise ValueError('Exactly one "name=type" pair should be supplied')
_name, _type = init_dict.items()[0]
def outer(function):
@wraps(function)
def inner(*args, **kwargs):
_value = _type()
function.func_globals[_name] = _value
function(*args, **kwargs)
return _value
return inner
return outer
@init_and_return(errors=list)
def checkDictionary(dictionary):
for key, value in dictionary.items():
if value == 'old_value':
errors.append(fixDictionary(dictionary, key, 'new_value'))
现在,最终用法如下所示:
dictionary = {'key': 'old_value'}
errors = checkDictionary(dictionary) # [<function fixDictionary at 0x01806C30>]
errors[0]()
print(dictionary) # {'key': 'new_value'}
这也很好用,让我也避免为这些函数编写更多样板。我对上面的实现有两个问题:
- 是否有更 Pythonic 的方法来实现此功能?目的是消除每个函数中的所有样板代码,但是编写函数
strip_args
和init_and_return
肯定会让大脑紧张。虽然这样的函数不必经常编写,但它们似乎与实际行为相去甚远。 - 第
function.func_globals[_name] = _value
行有异常行为;它允许从全局范围访问errors
。这不是世界末日,因为每次调用函数时都会重置变量,但是无论如何我都可以设置局部变量吗?我已尝试将此行更改为locals()[_name] = _value
,但作用域并未传递给函数。这种级别的元编程是否超出了 Python 的预期范围?
我想出了一个方法来解决我的第二个问题,方法是在 init_and_return
函数中实现一些簿记代码,检查它是否正在覆盖全局变量,然后在覆盖时恢复它(或者在覆盖时删除它)不是)。
def init_and_return(**init_dict):
# this could be extended to allow for more than one k-v argument
if len(init_dict) != 1:
raise ValueError('Exactly one "name=type" pair should be supplied')
_name, _type = init_dict.items()[0]
def outer(function):
@wraps(function)
def inner(*args, **kwargs):
# instantiate a new container
_value = _type()
# used to roll-back the original global variable
_backup, _check = None, False
# store original global variable (if it exists)
if _name in function.func_globals:
_backup = function.func_globals[_name]
_check = True
# add container to global scope
function.func_globals[_name] = _value
function(*args, **kwargs)
# roll-back if it existed beforehand, delete otherwise
if _check:
function.func_globals[_name] = _backup
else:
del function.func_globals[_name]
return _value
return inner
return outer