通过getattr访问方法

时间:2018-08-15 12:24:42

标签: python oop methods attributes

我偶然发现了这种行为,这表明您可以使用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的评论澄清了上面的大部分内容。我唯一不了解的是文档摘录:

  

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

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

2 个答案:

答案 0 :(得分:4)

是的,方法仅仅是包含适当形式的函数的属性(它们必须接受至少一个参数,即接收者,通常称为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>>

这可以说是“将实例对象和函数对象打包(指向它们的指针)”:实例对象<__main__.Foo instance at 0x10ba11320>(又名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(即str)的str.join属性。这使我们在第一行和第二行之间有了清晰的翻译。

答案 1 :(得分:2)

是的,方法是属性。

来自the python glossary

  

属性

     

与对象关联的值,该名称使用点分表达式按名称引用。例如,如果对象o具有属性   它会被称为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_method的类中获取 function (!)some_object,然后将其转换为绑定方法,其中隐含了self参数。他们为什么称其为“抽象对象”,这对我来说还是个谜。无论如何,有关此过程的更多详细信息,请参见How does assignment of a function as a class attribute become a method in Python?the relevant section in the descriptor HowTo

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