您的位置:首页 > 编程语言 > Python开发

16.4 Python descriptor-function and method

2016-12-06 23:52 501 查看

Python descriptor - function and method

Python descriptor - function and method
准备知识

function and method

静态方法

类方法

类方法静态方法实例方法

参考文档

转载请标明出处(http://blog.csdn.net/lis_12/article/details/53495627).

properties
,
methods
,
static methods
,
class methods
, and
super()
都是基于描述符实现的。本篇文章了解下就好,不必深究,如果没学过描述符的话,建议研究下描述符


准备知识

访问属性优先级: 类属性的数据描述符 > 实例属性 > 类属性的非数据描述符,非描述符的类属性 >
__getattr__()


如果一个对象定义了
__get__()
方法,在属性访问时会覆盖默认行为,调用
__get__()
;(这个对象要为类属性)

function and method

​ Python面向对象的特征是建立在函数上的,非数据描述符将二者完美的结合在了一起。

类的字典将类中的方法当做函数存储。在定义类的时候,方法通常用关键字
def
lambda
来声明,这和创建函数的方式是一样的。唯一的不同之处是方法的第一个参数用来表示实例,Python约定,这个参数通常是 self, 也可以是 this 或者其它任何名字。

个人认为 方法就是一种加了命名空间的特殊函数,命名空间就是实例。

​ 为了支持方法调用,函数定义了
__get__()方法
,即当函数作为属性被访问时会调用
function.__get__()
。所有的函数都是非数据描述符,它们返回绑定(bound)还是非绑定(unbound)方法取决于是被实例调用还是被类调用。


绑定方法: 函数中的第一个参数已经被设置成实例;

未绑定方法: 所有参数原封不动地传给原来的函数,包括第一个参数self;

Python模拟函数的实现

class function(object):
. . .
def __get__(self, obj, objtype = None):
"Simulate func_descr_get() in Objects/funcobject.c"
return types.methodType(self, obj, objtype)


因为function为非数据描述符,当function对象作为属性被访问时会调用
function.__get__()


class Foo(object):
def __init__(self):
self.x = 1
def fun(self):
print 'fun'
def f1():
pass
f = Foo()


方法在类字典中的存储方式为函数

print f1                                     #<function fun at>
print type(f1)                               #<type 'function'>
print Foo.__dict__['fun']                    #<function fun at>,与f1的结果一样,虽然是类中的方法,但是按照函数存储的
print type(Foo.__dict__['fun']) == type(f1)  #True,相等啊,记住方法是按照函数存储的...


此时
Foo.__dict__['fun']
就是一个函数…只不过第一参数要传Foo的实例,不然可能会出现异常。

按函数的形式调用方法

print Foo.__dict__['fun'](f)                 #fun,将实例以参数的形式传给fun函数,等价于f.fun()


绑定方法和非绑定方法

print f.fun          #bound method,此时已经将第一个参数设置成了实例f
print Foo.fun        #unbound method,参数原封不动
f.fun()              #调用fun方法

#Foo.fun()           #error,缺少实例参数,即self

Foo.fun(f)           #加上实例参数,等价于f.fun(),有没有感觉很和函数一样...,只不过多了个命名空间


函数为非数据描述符

print '__get__' in dir(f1)                  #True
print '__get__' in dir(Foo.__dict__['fun']) #True


类中的函数作为属性被访问时,描述符方法
__get__()
会将函数转化为方法,即当调用f.fun时,编译器会将
f.fun
转化为
Foo.__dict__['fun'].__get__(f,type(f))
,也就是说f.fun为
Foo.__dict__['fun'].__get__(f,type(f))
的返回值。

bound = Foo.__dict__['fun'].__get__(f,type(f))
bound1 = Foo.__dict__['fun'].__get__(f)
unbound = Foo.__dict__['fun'].__get__(None,Foo)
print bound == f.fun         #True
print bound1 == f.fun        #True
print unbound == Foo.fun     #True


由以上代码可知,

方法在类字典中的存储方式为函数;

绑定方法和非绑定方法是两个不同的类型。

绑定方法,由函数转化为绑定方法时,函数的第一个参数设置成实例,其余的参数为绑定方法的参数;

非绑定方法,由函数转化为非绑定方法时 函数中的参数原封不动的传给方法。

函数为非数据描述符,当类中的函数作为属性被访问时(即访问类中的方法),会调用
function.__get__()
function.__get__()
的返回值为方法;



函数和方法测试code

class Foo(object):
def __init__(self,x = 1):
self.x = x
def fun(self,x):
print 'fun:self.x = %s;x = %s'%(self.x,x)
def fun():
pass

f = Foo()
f.fun(2)              #fun:self.x = 1;x = 2

#传参二重奏,先将实例传进去,然后再将其他参数传进去
Foo.__dict__['fun'].__get__(f,type(f))(2) #fun:self.x = 1;x = 2,等价于f.fun(2)

#验证
F = type(f).__dict__['fun'].__get__(f,type(f))
print type(F)         #<type 'instancemethod'>
print F == f.fun      #True
F(3)                  #fun:self.x = 1;x = 3


以下为个人理解.

传参二重奏:第一阶段先给self赋值,第二阶段给除self以外的参数赋值…

fun
中的
self
参数为
fun
所在的命名空间,
实例f
就是一个命名空间;

F = type(f).__dict__['fun'].__get__(f,type(f))
先给
fun中的self赋值
,相当于传参只传了一部分,即只给self赋值了(self = f),其他参数未赋值,此时F就为一个
特殊的
普通函数
,可以像普通函数一样调用…

继续给其他参数赋值,即给除了self之外的参数赋值,如调用函数F,
F(3)


静态方法

那些不需要
self
cls
变量的方法适合为静态方法。


staticmethod()
Python的模拟实现:

class staticmethod(object):
"Emulate Pystaticmethod_Type() in Objects/funcobject.c"

def __init__(self, f):
self.f = f

def __get__(self, obj, objtype=None):
return self.f


因为staticmethod为非数据描述符,当staticmethod对象作为属性被访问时会调用
staticmethod.__get__()


class E(object):
def f(x):
print x
f = staticmethod(f)


静态方法的存储方式

print E.__dict__['f']  #<staticmethod object at>


静态方法与实例方法的存储方式不一样,不是按照函数形式存储的。

从类和实例中调用静态方法

e = E()
E.f(3)             #3
e.f(3)             #3

#E.__dict__['f'](3) #TypeError: 'staticmethod' object is not callable,不能利用这种方式调用


静态方法是非数据描述符

print '__get__' in dir(E.__dict__['f']) #True


静态方法作为属性被访问时会调用
staticmethod.__get__()


'''第一个参数为实例还是None对静态方法没影响,只要类对了就ok了'''
a = E.__dict__['f'].__get__(None,E)
b = E.__dict__['f'].__get__(e,E)

print a == E.f   #True
print b == E.f   #True


从以上代码可知,

静态方法在类字典中的存储方式与实例方法不同;

静态方法为非数据描述符,当静态方法作为属性被访问时会调用
staticmethod.__get__()


静态方法与实例无关,与类有关…

类方法

与静态方法不同,类方法的第一个参数用来表示类,一般为cls。

classmethod()
Python模拟实现:

class classmethod(object):
"Emulate Pyclassmethod_Type() in Objects/funcobject.c"
def __init__(self, f):
self.f = f

def __get__(self, obj, klass=None):
if klass is None:
klass = type(obj)   #注意这里...
def newfunc(*args):
return self.f(klass, *args)
return newfunc


因为classmethod为非数据描述符,当classmethod对象作为属性被访问时会调用
classmethod.__get__()


class E(object):
def f(cls,x):
print x
f = classmethod(f)
e = E()


类方法的存储方式

print E.__dict__['f']   #<classmethod object at 0x00000000033FB168>


类方法的存储方式与静态方法,实例方法都不同。

从类和实例调用类方法

e.f(3)  #3
E.f(3)  #3

#E.__dict__['f']()    #TypeError: 'classmethod' object is not callable,不能用这种方式调用


与实例方法的self一样,cls参数已经自动传入类方法了,无需手动传入。

类方法是非数据描述符

print '__get__' in dir(E.__dict__['f'])  #True


类方法作为属性被访问时会调用
classmethod.__get__()


a = E.__dict__['f'].__get__(None,E)  #<bound method type.f of <class '__main__.E'>>
b = E.__dict__['f'].__get__(e,E)     #<bound method type.f of <class '__main__.E'>>
c = E.__dict__['f'].__get__(E,E)     #<bound method type.f of <class '__main__.E'>>
d = E.__dict__['f'].__get__(E)       #<bound method type.f of <type 'type'>>
print a == E.f                       #True
print b == E.f                       #True
print c == E.f                       #True
print d == E.f                       #False


由上述代码可知,

类方法的存储方式与静态方法,实例方法都不同;

类方法也为非数据描述符,当类方法作为属性被访问时会调用
classmethod.__get__()


类方法与实例无关,与类有关…

类方法相比于静态方法的优势

当一个函数不需要相关的数据做参数而只需要一个类的引用的时候,这个特征就显得很有用了。类方法的一个用途是用来创建不同的类构造器。在Python 2.3中,
dict.fromkeys()
可以依据一个key列表来创建一个新的字典。等价的Python实现就是:

class Dict:
. . .
def fromkeys(klass, iterable, value=None):
"Emulate dict_fromkeys() in Objects/dictobject.c"
d = klass()
for key in iterable:
d[key] = value
return d
fromkeys = classmethod(fromkeys)

#现在,一个新的字典就可以这么创建:

>>> Dict.fromkeys('abracadabra')
{'a': None, 'r': None, 'b': None, 'c': None, 'd': None}


类方法,静态方法,实例方法

class Foo(object):
def inst_f(self,a):
print 'inst_fun()',a

@classmethod
def class_f(cls,a):
print 'class fun()',a

@staticmethod
def static_f(a):
print 'static fun()',a
f = Foo()


存储方式

print type(Foo.__dict__['inst_f'])   #<type 'function'>
print type(Foo.__dict__['class_f'])  #<type 'classmethod'>
print type(Foo.__dict__['static_f']) #<type 'staticmethod'>


以函数的形式调用(从类调用方法)

Foo.inst_f(f,1)                      #inst_fun() 1,等价于f.inst_f()
Foo.class_f(2)                       #class fun() 2,等价于f.class_f(2)
Foo.static_f(3)                      #static fun() 3,等价于static_f(3)


从实例调用方法

f.inst_f(1)                          #inst_fun() 1,等价于f.inst_f()
f.class_f(2)                         #class fun() 2,等价于f.class_f(2),self自动传入
f.static_f(3)                        #static fun() 3,等价于static_f(3),cls自动传入


从类和实例调用区别

print f.inst_f,Foo.inst_f           #bound method,unbound method
print f.inst_f == Foo.inst_f        #False

print f.class_f,Foo.class_f         #bound method,bound method,两种形式是一样的
print f.class_f == Foo.class_f      #True

print f.static_f,Foo.static_f       #function static_f,function static_f,两种是一样的
print f.static_f == Foo.static_f    #True


从以上代码可知,

1) 调用实例方法时,从类和实例调用是不一样的,即实例方法与实例有关;

2) 调用类方法和静态方法时,从类和实例调用是一样的,即类方法和静态方法与实例无关。

利用
__get__()
调用方法

Foo.__dict__['inst_f'].__get__(f,type(f))(1)      #inst_fun() 1
Foo.__dict__['static_f'].__get__(None,type(f))(2) #static fun() 2
Foo.__dict__['class_f'].__get__(None,type(f))(3)  #class fun() 3


参考文档

https://docs.python.org/2/howto/descriptor.html#definition-and-introduction

http://blog.csdn.net/lis_12/article/details/53453665

http://pyzh.readthedocs.io/en/latest/Descriptor-HOW-TO-Guide.html

http://stackoverflow.com/questions/114214/class-method-differences-in-python-bound-unbound-and-static
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息