关于python:通过getattr访问方法

Accessing methods via getattr

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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'。但我从来没有听说过被称为类或类实例属性的方法,例如,在Python中什么是"方法"中没有提到这一点?

文档中有一个解释提到了"数据属性"和"非数据属性"之间的区别,但我没有理解。

更新:由@amadan发表的评论澄清了上述大部分内容。我唯一不明白的一点是从文档中摘录的:

If you still don’t understand how methods work, a look at the
implementation can perhaps clarify matters. When a non-data attribute
of an instance is referenced, the instance’s class is searched. If
the name denotes a valid class attribute that is a function object, a
method object is created by packing (pointers to) the instance object
and the function object just found together in an abstract object:
this is the method object.

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


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

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

1
2
3
4
5
6
7
class Foo():
    def a(self):
        print("FOO")

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

因此,def实际上在python 2中附加了一个属性aFoo,作为"未绑定方法"(正如我们检查它时看到的那样,它还不知道是谁接收它),或者只是一个普通函数值(在python 3中):

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

您可以像调用任何函数一样调用它(…有一个例外,在python 2中:第一个参数必须是Foo类型:

1
2
3
4
5
6
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)

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

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

这可以说是"将实例对象和函数对象打包在一起":在一个包中有一个对实例对象的引用,即<__main__.Foo instance at 0x10ba11320>(a.k.a.Foo)和一个对函数对象的引用,即Foo.a,我们称之为"绑定方法"。

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

1
2
3
4
// 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中,绑定函数在其内部携带其接收器:

1
2
3
// 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`

这个电话怎么用?段落的其余部分解释了:

When the method object is called with an argument list, a new argument list is constructed from the instance object and the argument list, and the function object is called with this new argument list.

所以,当我们说

1
foo.a()

它将把Foo.a解包成Foo.aFoo;把接收者Foo预先放在参数列表中(这里没有,所以参数列表是[foo] + []作为[foo]的最终参数列表),最后被称为Foo.a(foo)。顺便说一下,这正是您可以手动执行的操作:

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

即使有内置对象:

1
2
3
4
"-".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中的函数。)EDOCX1的属性〔22〕(即str.join)。这给了我们一个清晰的第一行和第二行之间的翻译。


是的,方法是属性。

从python词汇表中:

attribute

A value associated with an object which is referenced by name using dotted expressions. For example, if an object o has an attribute
a it would be referenced as o.a.

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

此外,在getattr文档中还有一句话:

getattr(x, 'foobar') is equivalent to x.foobar

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

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

  • 方法的用途与其他属性完全不同。方法旨在被调用,而其他属性通常只是数据。这也是为什么我们不把函数称为"变量"的原因,尽管它们在技术上是如此。
  • "方法"比"可调用类属性"更容易说。
  • 最后,关于数据属性与非数据属性:文档对方法(即可调用属性;"非数据属性")和数据属性(即其他所有属性)进行了区分。

    There are two kinds of valid attribute names, data attributes and methods.

    data attributes correspond to"instance variables" in Smalltalk, and to"data members" in C++.

    The other kind of instance attribute reference is a method.

    您发布的摘录相当令人困惑,但我认为它试图提供描述符协议的基本解释,描述符协议负责将函数转换为方法。我们再来看看:

    When a non-data attribute of an instance is referenced, the instance’s
    class is searched. If the name denotes a valid class attribute that is
    a function object, a method object is created by packing (pointers to)
    the instance object and the function object just found together in an
    abstract object: this is the method object.

    换句话说:当您执行some_object.some_method操作时,python将获取函数(!)some_method来自some_object的类,然后将其转换为一个绑定方法,其中隐式包含self参数。他们为什么称之为"抽象物体"对我来说是个谜。无论如何,有关这个过程的更多详细信息,请参阅在Python中如何将函数赋值为类属性成为方法?或者描述符howto中的相关部分。

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