【问题标题】:Accessing methods via getattr通过 getattr 访问方法
【发布时间】:2019-01-22 07:51:58
【问题描述】:

我偶然发现了这种行为,这表明您可以使用getattr 来调用类实例上的方法,以替代直观命名的operator.methodcaller

from operator import methodcaller

class Foo():
    def __init__(self, lst):
        self.lst = lst

    def summer(self):
        return sum(self.lst)

my_obj = Foo(range(11))

res1 = methodcaller('summer')(my_obj)  # 55
res2 = getattr(my_obj, 'summer')()     # 55

assert res1 == res2

我想了解,在内部,为什么这有效。是不是因为所有的方法也是属性?这似乎是这样的,因为dir(Foo)dir(my_obj) 包括'summer'。但我从未听说过称为类或类实例的 attributes 的方法,例如What is a “method” in Python?中没有提到这一点

有一个explanation in the docs 提到了我无法理解的“数据属性”和“非数据属性”之间的区别。

更新:@Amadan 的评论澄清了上述大部分内容。剩下的唯一一点我不明白的是文档摘录:

如果您仍然不了解方法的工作原理,请查看 实施也许可以澄清问题。当一个非数据属性 引用实例的,搜索实例的类。如果 该名称表示作为函数对象的有效类属性, 方法对象是通过打包(指向)实例对象来创建的 和刚刚在抽象对象中找到的函数对象: 这是方法对象。

那么非数据属性是通过检查它是否可调用来确定的,还是有其他方法可以用来确定它是一个函数对象?指向实例对象的“打包指针”是什么意思?什么是抽象对象?

【问题讨论】:

  • Here: "另一种实例属性引用是方法。"非常明确地说:有些属性只是数据,但有些属性是方法。
  • 看看文档怎么说methodcaller is equivalent to :)
  • @Amadan,谢谢!我想要澄清的唯一剩下的事情是文档中关于数据与非数据属性的评论。我稍后会更新我的问题。

标签: python oop methods attributes


【解决方案1】:

是的,方法只是包含适当形式的函数的属性(它们必须接受至少一个参数,接收者,通常称为self)。

这是一个解释引用段落的示例:

class Foo():
    def a(self):
        print("FOO")

foo = Foo()
foo.a()
# => FOO

所以,def 实际上将属性a 附加到Foo 作为“未绑定方法”(正如您在我们检查它时看到的那样 - 意思是,它还不知道谁在接收它) Python 2,或者只是一个普通的函数值(在 Python 3 中):

Foo.a
# => <unbound method Foo.a>          (Python 2)
# => <function Foo.a at 0x10919fea0> (Python 3)

您可以像调用任何函数一样调用它(...除了一个例外,在 Python 2 中:第一个参数必须是类型为 Foo):

Foo.a(foo)
# => FOO

Foo.a(42)
# => TypeError: unbound method a() must be called with Foo instance as first argument (got int instance instead) (Python 2)
# => 42 (Python 3)

但是,如果您尝试在实例上找到它(“实例属性引用”),它现在报告为“绑定方法”:

foo.a
# => <bound method Foo.a of <__main__.Foo instance at 0x10ba11320>>

这可以说是“将实例对象和函数对象打包(指针)在一起”:有对实例对象的引用&lt;__main__.Foo instance at 0x10ba11320&gt;(又名foo),还有对函数的引用对象Foo.a,在一个包中我们称为“绑定方法”。

与 JavaScript 不同,它不是纯粹的语法。在 JavaScript 中,方法调用和函数调用的区别在于调用本身:如果它有一个点,那就是方法调用:

// JavaScript
let foo = new Foo()
foo.a();                // method invocation: `a` is told that the receiver is `foo`
let z = foo.a; z()      // function invocation, `a` doesn't know about `foo`

在 Python 中,绑定函数在其中携带其接收器:

// Back to Python
foo.a()                 // method invocation: `a` is told that the receiver is `foo`
z = foo.a; z()          // STILL method invocation; `z` knows both `foo` and `Foo.a`

这个调用是如何工作的?该段的其余部分解释:

当使用参数列表调用方法对象时,会从实例对象和参数列表中构造一个新的参数列表,并使用这个新的参数列表调用函数对象。

所以,当我们说

foo.a()

它将foo.a解压成Foo.afoo;将接收者foo 添加到参数列表中(我这里没有,所以参数列表是[foo] + [] 用于[foo] 的最终参数列表),最终被调用的是Foo.a(foo)。顺便说一句,这正是您可以手动执行的操作:

Foo.a(foo)
# => FOO

即使是内置对象:

"-".join(["foo", "bar", "baz"])
# => 'foo-bar-baz'
str.join("-", ["foo", "bar", "baz"])
# => 'foo-bar-baz'

这里,"-".join 是一个绑定方法,它将接收者"-" 和函数str.join 打包在一起;当我们调用第一行时,接收者"-" 被添加到["-", ["foo", "bar", "baz"]] 的最终参数列表的其余参数[["foo", "bar", "baz"]] 之前,并将其发送到位于@ 的join 属性中的函数987654352@(即str.join)。这让我们在第一行和第二行之间有了清晰的翻译。

【讨论】:

  • 我只想指出,python 3 中不再存在未绑定的方法。python 2 中的“未绑定方法”现在只是一个普通的旧函数。因此,self 也不再是该类的实例 - Foo.a(42) 将在 python 3 中打印“FOO”。
  • @Aran-Fey Ah derp。我知道我会在某处搞砸。谢谢!
【解决方案2】:

是的,方法就是属性。

来自the python glossary

属性

与对象关联的值,该对象使用点分表达式按名称引用。例如,如果一个对象 o 有一个属性 a 它将被称为 o.a.

显然我们可以访问这样的方法,因此它们必须是属性。它们只是恰好是函数的属性。

另外,the getattr documentation中有这句话:

getattr(x, 'foobar') 等价于x.foobar

因此,x.foobar() 也等同于 getattr(x, 'foobar')()。没有理由相信方法在任何方面都是特殊的。


在实践中方法很少被称为属性的原因,我认为有两个方面:

  1. 方法的用途与其他属性完全不同。方法旨在被调用,而其他属性通常只是数据。这与我们不将函数称为“变量”的原因相同,尽管它们在技术上是这样。
  2. “方法”比“可调用的类属性”更容易说。

最后,关于数据属性与非数据属性:The documentation 区分了方法(即可调用属性;“非数据属性”)和数据属性(即其他所有属性)。

有效的属性名有两种,数据属性和方法。

数据属性对应于 Smalltalk 中的“实例变量”,以及 C++ 中的“数据成员”。

另一种实例属性引用是方法。

您发布的摘录相当混乱,但我认为它试图提供descriptor protocol 的基本解释,它负责将函数转换为方法。让我们再看一遍:

当引用实例的非数据属性时,该实例的 类被搜索。如果名称表示一个有效的类属性,即 一个函数对象,一个方法对象是通过打包创建的(指针) 实例对象和函数对象刚刚在一个 抽象对象:这是方法对象。

换句话说:当您执行some_object.some_method 时,python 从some_object 的类中获取函数(!) some_method,然后将其转换为绑定方法 其中隐含包含 self 参数。为什么他们称它为“抽象对象”对我来说是个谜。无论如何,有关此过程的更多详细信息,请参阅 How does assignment of a function as a class attribute become a method in Python?the relevant section in the descriptor HowTo

(警告:Python 区分数据描述符非数据描述符。不要将它们与数据属性混淆 和非数据属性!这是两个完全不相关的概念。)

【讨论】:

    猜你喜欢
    • 2015-04-22
    • 2011-04-17
    • 2019-12-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多