本篇主要总结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 "<stdin>", line 1, in <module>
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 "<stdin>", line 1, in <module>
TypeError: foo() takes no arguments (1 given)

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

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

>>> Foo.foo
<unbound method Foo.foo>
>>> Foo().foo
<bound method Foo.foo of <__main__.Foo object at 0x7ff33b424d50>>

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

>>> type(Foo.foo)
<type 'instancemethod'>
>>> type(Foo().foo)
<type 'instancemethod'>

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

2. bound/unbound method是怎么来的

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

>>> Foo.__dict__
dict_proxy({'__dict__': <attribute '__dict__' of 'Foo' objects>, '__module__': '__main__', 'foo': <function foo at 0x7ff33b42a5f0>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None})

在其中我们看到了'foo': <function foo at 0x7ff33b42a5f0>。然后利用字典查看foo:

>>> Foo.__dict__['foo']
<function foo at 0x7ff33b42a5f0>

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

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

>>> Foo.__dict__['foo'].__get__(None,Foo)
<unbound method Foo.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)
<bound method Foo.foo of <__main__.Foo object at 0x7ff33b424d50>>

因此,可以有如下理解:

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

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 "<stdin>", line 1, in <module>
TypeError: unbound method foo() must be called with Foo instance as first argument (got nothing instead)
>>> Foo().foo()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: foo() takes no arguments (1 given)
>>> Foo.foo_one()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
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 "<stdin>", line 1, in <module>
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)
<type 'function'>

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

总结

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

[转载]Python方法绑定——Unbound/Bound method object的一些梳理的更多相关文章

  1. 使用MethodType函数将方法绑定到类或实例上

    在开始正文之前,需要了解下Python的绑定方法(bound method)和非绑定方法. 简单做个测试: 定义一个类,类中由实例方法.静态方法和类方法. class ClassA: def inst ...

  2. python tips:类的绑定方法(bound)和非绑定方法(unbound)

    类属性只有类及其实例能够访问,可以理解为一个独立的命名空间. Python中类属性的引用方式有两种: 1. 通过类的实例进行属性引用,称为绑定方法(bound method),可以理解为方法与实例绑定 ...

  3. Bound Method and Unbound Method - 绑定方法 与 非绑定方法

    Bound Method and Unbound Method 通常有两种方法对类的方法(instance.method)/属性(class.attribute)进行引用, 一种称做 Bound Me ...

  4. [Python] Python 之 function, unbound method 和 bound method

    首先看一下以下示例.(Python 2.7) #!/usr/bin/env python # -*- coding: utf-8 -*- class C(object): def foo(self): ...

  5. Python编程-绑定方法、软件开发

    一.绑定方法与非绑定方法 1.绑定方法 绑定给谁,谁来调用就自动将它本身当作第一个参数传入 (1)绑定到类的方法:用classmethod装饰器装饰的方法. 为类量身定制 类.boud_method( ...

  6. Dynamic Method Binding in Delphi 动态方法绑定

    Dynamic Method Binding in Delphi  动态方法绑定 https://docs.dataabstract.com/Delphi/AdvancedTopics/Dynamic ...

  7. python方法中的self

    前几天在写一个c作业时,突发奇想,在结构体中加入函数指针, 像这样: struct People { int _age; int (*age)(); }people; 这样调用时就可以 people. ...

  8. python方法未绑定错误

    相信 Python 程序员多多少少都和我一样遇到过 Method Unbound Error,直译过来就是 “方法未绑定错误”,虽然搜索之后知道了使用 @classmethod 这样的装饰起后就可以解 ...

  9. [转载]Python: 你不知道的 super

    原文出处: geekvi super() 的入门使用 在类的继承中,如果重定义某个方法,该方法会覆盖父类的同名方法,但有时,我们希望能同时实现父类的功能,这时,我们就需要调用父类的方法了,可通过使用  ...

随机推荐

  1. TX2开发板Ubuntu16.04安装中文输入法

    打开终端输入安装输入法: sudo apt-get install fcitx fcitx-googlepinyin fcitx-module-cloudpinyin fcitx-sunpinyin ...

  2. python运算表达式

    运算符1.算术运算符:+,-,*,/,//(求整商),%,**(求多次方,左边为数,右边为多少次方)2.关系运算符:>,<,==,<=,>=,!=3.测试运算:in,not i ...

  3. centos7+nginx+php+mysql环境搭建

    一:CentOS7安装 在VMware 新建一个虚拟机CentOS 64位,配置好磁盘大小为30G,内存2G,启动虚拟机进入CentOS安装界面 选择Install CentOS 7 SOFTWARE ...

  4. 如何搞定Critical Thinking写作?

    受中国传统教育模式与国外一流大学之间的差异的影响,在海外留学的学子们常常会在新的学习生活中面临许多难题,Critical Thinking就是其中之一.国内的教育方法常常以灌输式的教育模式为主,忽略了 ...

  5. redis(二)----基本操作

    1. redis介绍 redis是一个key-value存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合).zset( ...

  6. jquery的读、写、增、删、查方法

    # 注:jquery需要导包  格式<script type="text/javascript" src="jquery-3.2.1.js">> ...

  7. 前后端分离java、jwt项目进行CORS跨域、解决非简单请求跨域问题、兼容性问题

    情况描述: 最近在部署一个前后端分离的项目出现了跨域问题*, 项目使用jwt进行鉴权,需要前端请求发起携带TOKEN的请求*,请求所带的token无法成功发送给后端, 使用跨域后出现了兼容性问题:Ch ...

  8. CTF -攻防世界-crypto新手区(1~4)

    题目已经提示用base64直接解密就行 base64解密网址 http://tool.oschina.net/encrypt?type=3 题目提示是凯撒密码 http://www.zjslove.c ...

  9. dp--分组背包 P1757 通天之分组背包

    题目背景 直达通天路·小A历险记第二篇 题目描述 自01背包问世之后,小A对此深感兴趣.一天,小A去远游,却发现他的背包不同于01背包,他的物品大致可分为k组,每组中的物品相互冲突,现在,他想知道最大 ...

  10. POJ 1185 状态DP

    这个题目是个挺难表示的状态DP,因为不但要考虑上下还要考虑左右,在DP里面就没有什么下了咯,但也至少除了考虑左右还要考虑上 所以先枚举出在同一行满足条件的状态 即 某状态 若 s&(s< ...