资讯专栏INFORMATION COLUMN

[转载]Python方法绑定——Unbound/Bound method object的一些梳理

yankeys / 3274人阅读

摘要:本篇主要总结中绑定方法对象和未绑定方法对象的区别和联系。在中使用描述器有翻译的链接来表示具有绑定行为的对象属性,使用描述器协议方法来控制对具有绑定行为属性的访问,这些描述器协议方法包括和。其中通过限定的必须使用实例才能调用。

本篇主要总结Python中绑定方法对象(Bound method object)和未绑定方法对象(Unboud method object)的区别和联系。
主要目的是分清楚这两个极容易混淆的概念,顺便将Python的静态方法,类方法及实例方法加以说明

OK,下面开始

1. 一个方法引发的“血案”

类中所定义的函数称为方法
举例:

>>>class Foo(object):
...        def  foo():
...            print "call foo"

然后令人困惑的地方就来了:
当你尝试使用类名.方法名调用函数foo时,会出现如下错误

>>> Foo.foo()
Traceback (most recent call last):
  File "", line 1, in 
TypeError: unbound method foo() must be called with Foo instance as first argument (got nothing instead)

看一下报错信息发现需要一个Foo的实例(instance)来调用,OK,于是调用如下:

>>> Foo().foo()
Traceback (most recent call last):
  File "", line 1, in 
TypeError: foo() takes no arguments (1 given)

-.-!!!
估计脾气不好的看到做到这里想要骂街了。
因为从字面上看Foo( ).foo( )并没有传递任何参数,而报错信息却显示(1 given)。

在Python中一切皆对象,方法是函数,所以我们来仔细查看一下函数对象foo

>>> Foo.foo

>>> Foo().foo
>

咦~,发现一个有趣的现象:
通过类名Foo获取类函数属性foo时,得到的是unbound method object,通过实例Foo()获取类的函数属性foo时,得到的是bound method object。
在来看看这两个对象的类型:

>>> type(Foo.foo)

>>> type(Foo().foo)

于是我们产生了更大的疑问:为什么同样是实例方法(instancemethod),获取方式的不同,会导致获得不同的对象呢?

2. bound/unbound method是怎么来的

下面让我们来一层层揭开这个bound/unbound method的面纱。
首先,我们知道,对于类,其属性是存放在__dict__字典中,即:

>>> Foo.__dict__
dict_proxy({"__dict__": , "__module__": "__main__", "foo": , "__weakref__": , "__doc__": None})

在其中我们看到了"foo":
然后利用字典查看foo:

>>> Foo.__dict__["foo"]

可以看到foo是一个函数对象,根据上一小节最后一个例子的信息,我们发现,foo是有绑定行为的。

在Python中使用描述器(有翻译的链接)来表示具有“绑定”行为的对象属性,使用描述器协议方法来控制对具有绑定行为属性的访问,这些描述器协议方法包括:__get__()、__set__()和__delete__()。
根据上面这段难以让人理解的描述,我们可以大胆的猜测,Foo的属性foo是一个描述器,它通过__get__()方法来控制对foo的访问。
根据描述器协议方法descr.__get__(self, obj, type=None) --> value,我们尝试如下:

>>> Foo.__dict__["foo"].__get__(None,Foo)

于是,我们惊讶的看到这个结果竟然与上一小节看到的结果相同!
这绝不是偶然。
事实上,根据官方文档的描述,调用Foo.foo时,Python会根据查找链从Foo.__dict__["foo"]开始,然后查找type(Foo).__dict__["foo"],一路向上查找type(Foo)的所有基类。Foo.foo会被转换为Foo.__dict__["foo"].__get__(None,Foo)。
也就是说,我们在代码中使用Foo.foo实际上会被转化成
Foo.__dict__["foo"].__get__(None,Foo)
对于根据描述器协议方法descr.__get__(self, obj, type=None) --> value的参数列表,由于其self参数在这里被赋予了None,所以没有给定实例,因此认为是未绑定(unbound)
(当然这是一种便于理解的描述,其根本机制请移步这里)
那么一个很简单的推理就是:如果self参数给定了实例对象,那么,得到的就是bound method,如下。

>>> Foo.__dict__["foo"].__get__(Foo(),Foo)
>

因此,可以有如下理解:

当通过类来获取函数属性的时候,得到的是非绑定方法对象
当通过实例来获取函数属性的时候,得到的是绑定方法对象

3. methods, static method and class method

如果有使用Python方法的经验,那么一定注意过self的使用,请看下面这个例子:

>>> class Foo(object):
...     def foo():
...             print "call foo"
...     def foo_one(self):
...             print "call foo_one"
... 
>>> Foo.foo()
Traceback (most recent call last):
  File "", line 1, in 
TypeError: unbound method foo() must be called with Foo instance as first argument (got nothing instead)
>>> Foo().foo()
Traceback (most recent call last):
  File "", line 1, in 
TypeError: foo() takes no arguments (1 given)
>>> Foo.foo_one()
Traceback (most recent call last):
  File "", line 1, in 
TypeError: unbound method foo_one() must be called with Foo instance as first argument (got nothing instead)
>>> Foo().foo_one()
call foo_one

这个例子定义了两个method:foo()和foo_one(self)。
可以看到,同样使用类名.方法名()调用时,所报的错误相同。但是在使用实例名.方法名()调用时,foo_one是可以调用成功的。
为什么呢?
原因在于当使用Foo().foo_one()调用时,Python做了如下修改:

>>> Foo.foo_one(Foo())
call foo_one

将实例Foo()作为第一个参数传递进去,因此,函数foo_one(self)调用成功。这也解释了为什么Foo().foo()调用不成功。
因为foo的定义为foo(),当调用Foo().foo()时,Python做了如下修改:

>>> Foo.foo(Foo())
Traceback (most recent call last):
  File "", line 1, in 
TypeError: foo() takes no arguments (1 given)

传入了一个参数Foo(),所以会出现foo() takes no arguments (1 given)的错误。
我曾经看到有的人把foo()这种参数列表中没有self的方法称为类方法,而把带有self的方法称为实例方法,根据上面的描述可以发现,这种划分是错误的。
那么,Python中有没有类方法呢?
答案是,有。那么如何定义一个类方法呢?

Pyhon 类方法

还是使用上面的例子

>>> class Foo(object):          
...     @classmethod                #定义类方法要点1
...     def foo(cls):               #定义类方法要点2
...             print "call foo"
... 
>>> Foo.foo()
call foo
>>> Foo().foo()
call foo

定义类方法需要注意两点:1. 添加@classmethod;2. 添加cls参数
这样定义的类方法可以通过类名.方法名()的形式调用,也可以通过实例.方法名()的形式调用。

看到这里会发现,在Python中定义方法,总要带两个参数self或者cls。其中通过self限定的method必须使用实例才能调用。
那么很自然的一个疑问是,能不能定义不包含self及cls的方法呢?像最开始的例子中foo()那样。答案是有的,办法就是加@staticmethod修饰器。
这种被@staticmethod修饰器修饰的方法,称为静态方法

Python 静态方法

除了类方法,还有静态方法,请看下面这个例子:

>>> class Foo(object):
...     @staticmethod
...     def foo():
...             print "call foo"
... 
>>> Foo.foo()
call foo
>>> Foo().foo()
call foo

静态方法可以通过类名.方法名()和实例.方法名()的形式调用。
查看type结果如下:

>>> type(Foo.foo)

可以看到,静态方法的类型是function,而类方法的类型是instancemethod。

总结

最后来总结一下:
从Python方法定义的角度出发,可以分为三种:
1.第一个参数是self;
2.第一个参数是cls;
3.参数既不含self也不含cls的
对于第一种方法,必须通过实例.方法名()或类名.方法名(实例)的形式调用;
对于第二种,可以通过实例.方法名()或类名.方法名()的形式调用,不能通过类名.方法名(实例)的形式调用;
对于第三种,方法即是普通函数,但是必须通过实例.方法名()或类名.方法名()的形式调用,不能通过其他形式调用

参考

转载自:简书-_Zhao_ -Python-Unbound/Bound method object
这篇博文讲解的非常清楚,值得一看

SF-python函数和类的一些研究

Python 中的方法绑定

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/45405.html

相关文章

  • 剖析Vue实现原理 - 如何实现双向绑定mvvm(转载

    摘要:接下来要看看这个订阅者的具体实现了实现订阅者作为和之间通信的桥梁,主要做的事情是在自身实例化时往属性订阅器里面添加自己自身必须有一个方法待属性变动通知时,能调用自身的方法,并触发中绑定的回调,则功成身退。 本文能帮你做什么?1、了解vue的双向数据绑定原理以及核心代码模块2、缓解好奇心的同时了解如何实现双向绑定为了便于说明原理与实现,本文相关代码主要摘自vue源码, 并进行了简化改造,...

    nemo 评论0 收藏0
  • 神坑·Python 装饰类无限递归

    摘要:如今查找结果有误,说明继承链是错误的,因而极有可能是出错。真相一切都源于装饰器语法糖。核心思路就是不要更改被装饰名称的引用。 本文首发于我的博客,转载请注明出处 《神坑》系列将会不定期更新一些可遇而不可求的坑防止他人入坑,也防止自己再次入坑 简化版问题 现有两个 View 类: class View(object): def method(self): #...

    spacewander 评论0 收藏0
  • PyTips 0x01 - 迭代器与生成器

    摘要:项目地址迭代器与生成器迭代器与生成器是中比较常用又很容易混淆的两个概念,今天就把它们梳理一遍,并举一些常用的例子。生成器前面说到创建迭代器有种方法,其中第三种就是生成器。 项目地址:https://git.io/pytips 迭代器与生成器 迭代器(iterator)与生成器(generator)是 Python 中比较常用又很容易混淆的两个概念,今天就把它们梳理一遍,并举一些常用的例...

    chemzqm 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<