我正在研究一个主要返回闭包而不是传统的基于类的方法的Python项目。例如:
def term(token):
def fn(text):
return (...)
return fn
你可以想象,调试和测试闭包是一个噩梦,特别是如果我在整个代码中使用相同的名称来封闭。例如:
>>> term('t')
<function fn at 0x...>
所以我尝试将它包装在一个类中,以便从一个namedtuple中获取特殊的__repr__
处理:
def rr(cls, attrs):
T = namedtuple(cls, attrs)
class G(object):
__slots__ = ()
def __init__(self, repr_message, callable):
self.callable = callable
self.repr_message = repr_message
def __call__(self, *args, **kwargs):
return self.callable(*args, **kwargs)
def __repr__(self):
return self.repr_message
K = type(cls, (G,), {})
def wrapper(fn):
def init(*args, **kwargs):
t = T(*args, **kwargs)
return K(
repr(t),
fn(*args, **kwargs),
)
return init
return wrapper
这样:
>>> rr('Term', ['token'])(term)('$')
Term(token='$')
你可以想象这可能会对性能产生影响。我的问题是,如果这种包装更可取,如果更多的是Pythonic,而不仅仅是返回“丑陋”的封闭?
答案 0 :(得分:6)
更轻松的方法是简单地修改闭包的func_name
属性。结果并不像代码生成的那样漂亮,但它对RAM和性能的影响很小。
def term(token):
def fn(text):
return text.split(token)
fn.func_name = "term(token={0!r})".format(token)
return fn
s = 'splitthistestup'
f = term('t')
g = term('i')
print(f, f(s))
print(g, g(s))
典型输出
<function term(token='t') at 0xb74878b4> ['spli', '', 'his', 'es', 'up']
<function term(token='i') at 0xb74878ec> ['spl', 'tth', 'stestup']
对于Python 3,您需要做一些稍微不同的事情。
fn.func_name = ...
变为:
fn.__qualname__ = ...
答案 1 :(得分:4)
我从你的问题中了解到你还在使用Python 2。
在Python 3(3.3+,因为我们正在使用inspect.getclosurevars
)这里,人们可以做一些严肃的魔法来做一个几乎没有开销的装饰器(这种情况下)会产生所需的输出;这在Python 2中更难做到,因为Python 2函数不知道它们的完全限定名称:
import inspect
class reprcorate(object):
__slots__ = ('__call__',)
def __init__(self, callable):
self.__call__ = callable
def __repr__(self):
func = self.__call__
funcname = func.__qualname__
funcname = funcname.replace('.<locals>', '')
try:
closure_vars = inspect.getclosurevars(func)
args = '(%s)' % ', '.join('%s=%r' % i for i in closure_vars.nonlocals.items())
last_part = '.' + func.__name__
if funcname.endswith(last_part):
funcname = funcname[:-len(last_part)]
except:
args = '(...)'
return funcname + args
def term(token):
@reprcorate
def fn(text):
@reprcorate
def fn2(fn2arg):
print(token, text, fn2arg)
return fn2
return fn
print(term('foo'))
print(term('foo')('bar'))
打印出来
term(token='foo')
term.fn(token='foo', text='bar')
这是如何工作的,可以为实例分配__call__
;所以我们不需要一个假的__call__
蹦床;我们还使用__slots__
来减少运行时开销。
真正的神奇之处在于__repr__
方法,其中装饰的函数/闭包被仔细剖析。可以在inspect.getclosurevars
的帮助下检查函数从外部作用域使用的自由变量,它返回一个命名元组;我们对nonlocals
特别感兴趣,因为这些是从外部范围使用的值;这是一个简单的变量名称到字典的字典。我们把它变成了一个很好的variable=value, variable=value
字符串。这些不是严格的函数参数,而是在内部闭包中看到和使用的值。
我们还清理了一下功能名称;在Python 3中,函数的名称位于__name__
,完全限定名称位于__qualname__
;封闭的__qualname__
看起来像foo.<locals>.bar.<locals>.baz
,因此我们删除了所有.<locals>
,以获得foo.bar.baz
;然后我们删除最后一个虚线部分,如果它与我们的包装函数的__funcname__
匹配,并在最后附加格式良好的参数,以获得真正好的结果。