__getattr__ 返回需要一个参数的 lambda 函数不起作用

__getattr__ returning lambda function requiring one argument does not work

我正在学习 Python 3 并且只是 运行 进入 getattr 函数。据我所知,当在 class 定义中找不到作为函数或变量的属性调用时,将调用它。

为了理解这种行为,我编写了以下测试 class(基于我读过的内容):

class Test(object):
    def __init__(self, foo, bar):
        self.foo = foo
        self.bar = bar

    def __getattr__(self, itm):
        if itm is 'test':
            return lambda x: "%s%s" % (x.foo, x.bar)
        raise AttributeError(itm)

然后我启动我的对象并调用不存在的函数 test,预计 returns 对函数的引用:

t = Test("Foo", "Bar")    
print(t.test)
<function Test.__getattr__.<locals>.<lambda> at 0x01A138E8>

但是,如果我调用该函数,结果不是预期的 "FooBar",而是一个错误:

print(t.test())
TypeError: <lambda>() missing 1 required positional argument: 'x'

为了获得预期的结果,我需要使用与第一个参数相同的对象来调用该函数,如下所示:

print(t.test(t))
FooBar

我发现这种行为相当 st运行ge,因为当调用 p.some_function() 时,据说添加 p 作为第一个参数。

如果有人能对我的这个头痛问题有所启发,我将不胜感激。我在 Eclipse 中使用 PyDev。

要得到你想要的,你需要一个不带参数的 lambda:

return lambda: "%s%s" % (self.foo, self.bar)

但是您真的应该为此使用 property

class Test(object):
    def __init__(self, foo, bar):
        self.foo = foo
        self.bar = bar

    @property
    def test(self):
        return "{}{}".format(self.foo, self.bar)

t = Test("Foo", "Bar")
print(t.test)
# FooBar

请注意缺少括号。


如果您绝对确定它必须是一个函数,请执行以下操作:

class Test(object):
    def __init__(self, foo, bar):
        self.foo = foo
        self.bar = bar

    @property
    def test(self):
        return lambda: "{}{}".format(self.foo, self.bar)

t = Test("Foo", "Bar")
print(t.test())
# FooBar

请注意,如果您这样做 print(t.__getattr__),您会得到类似 <bound method Test.__getattr__ of <__main__.Test object at 0x00000123FBAE4DA0>> 的结果。关键是在对象上定义的方法被称为 'bound' ,因此总是将对象作为第一个参数。您的 lambda 函数只是一个匿名函数而不是 'bound' 任何东西,因此要访问它需要显式传入的对象。

我认为您这样做只是为了试验使用“__getattr__”,因为通过使 lambda 成为对象上的方法,您可以更轻松地实现您正在做的事情。

"I find this behaviour rather strange, as when calling p.some_function(), is said to add p as the first argument."

some_function 实际上是一个 方法 ,这就是为什么当方法是 "bound to an object." 但是普通函数不是这样工作的,只有在 class 主体中定义的函数 才会自动对它们应用这种魔法 。实际上,未绑定方法(直接通过 class 访问)的功能与普通功能相同!术语 "bound and unbound" 方法不再适用,因为在 Python 3 中我们只有方法和函数(摆脱了未绑定方法和普通函数之间的区别)。实例化实例时,访问属性 returns 一个 方法 ,它在调用时隐式调用实例。

>>> class A:
...     def method(self, x):
...         return x
...
>>> a.method
<bound method A.method of <__main__.A object at 0x101a5b3c8>>
>>> type(a.method)
<class 'method'>

但是,如果您访问 class 的属性,您会发现它只是一个函数:

>>> A.method
<function A.method at 0x101a64950>
>>> type(A.method)
<class 'function'>
>>> a = A()

现在,观察:

>>> bound = a.method
>>> bound(42)
42
>>> unbound = A.method
>>> unbound(42)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: method() missing 1 required positional argument: 'x'

但这就是 classes 的魔力。请注意,您甚至可以动态地向 classes 添加函数,当您在实例上调用它们时,它们会神奇地变成方法:

>>> A.method2 = lambda self, x: x*2
>>> a2 = A()
>>> a2.method2(4)
8

而且,正如人们希望的那样,该行为仍然适用于已创建的对象!

>>> a.method2(2)
4

请注意,如果您动态添加到 实例,这不起作用

>>> a.method3 = lambda self, x: x*3
>>> a.method3(3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: <lambda>() missing 1 required positional argument: 'x'

你必须自己施展魔法:

>>> from types import MethodType
>>> a.method4 = MethodType((lambda self, x: x*4), a)
>>> a.method4(4)
16
>>>

您需要创建一些行为类似于绑定方法的东西,您可以简单地使用 functools.partial 将实例绑定到函数:

from functools import partial

class Test(object):
    def __init__(self, foo, bar):
        self.foo = foo
        self.bar = bar

    def __getattr__(self, itm):
        if itm == 'test':  # you shouldn't use "is" for comparisons!
            return partial(lambda x: "%s%s" % (x.foo, x.bar), self)
        raise AttributeError(itm)

测试:

t = Test("Foo", "Bar")    
print(t.test)
# functools.partial(<function Test.__getattr__.<locals>.<lambda> at 0x0000020C70CA6510>, <__main__.Test object at 0x0000020C7217F8D0>)
print(t.test())
# FooBar

__getattr__ return 值是 "raw",它们的行为不像 class 属性,调用普通方法涉及的描述符协议导致创建绑定方法(其中 self 被隐式传递)。要将函数绑定为方法,您需要手动执行绑定:

import types

...

def __getattr__(self, itm):
    if itm is 'test':  # Note: This should really be == 'test', not is 'test'
        # Explicitly bind function to self
        return types.MethodType(lambda x: "%s%s" % (x.foo, x.bar), self)
    raise AttributeError(itm)

types.MethodType is poorly documented(交互式帮助更有帮助),但基本上,您向它传递一个用户定义的函数和一个 class 的实例,它 return 是一个绑定方法调用时,隐式将该实例作为第一个位置参数(self 参数)传递。

请注意,在您的特定情况下,您可以仅依靠闭包范围来使零参数函数继续工作:

def __getattr__(self, itm):
    if itm is 'test':  # Note: This should really be == 'test', not is 'test'
        # No binding, but referring to self captures it in closure scope
        return lambda: "%s%s" % (self.foo, self.bar)
    raise AttributeError(itm)

现在它根本不是一个绑定方法,只是一个恰好从定义它的范围(__getattr__ 调用)捕获了 self 的函数。哪种解决方案最好取决于您的需求;创建一个绑定方法稍微慢一点,但是得到一个真正的绑定方法,而依赖闭包范围(通常,>400ns 中的 ~10ns)更快,但是 returns 是一个普通函数(这可能是一个问题,如果,例如,它作为回调传递给假设它是绑定方法的代码,并且可以分别提取 __self____func__。例如)。