是否应该添加额外的图层以支持__repr__?

时间:2015-08-29 09:02:52

标签: python python-2.x

我正在研究一个主要返回闭包而不是传统的基于类的方法的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,而不仅仅是返回“丑陋”的封闭?

2 个答案:

答案 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__匹配,并在最后附加格式良好的参数,以获得真正好的结果。