python 描述器_python描述器(Descriptor)

描述器协议

描述器协议包括以下3个方法:

object.__get__(self, instance, owner)

调用时得到类的属性(类属性访问控制)或者类的实例的属性(实例属性访问控制),owner是属性所在类,instance是属性所在类的实例或None(如果通过类进行访问的话)。该方法要么返回属性值,要么抛出AttributeError异常。

object.__set__(self, instance, value)

调用时给实例的属性赋予新值

object.__delete__(self, instance)

调用时删除实例的属性

描述器是什么

那什么是描述器呢?一般来讲,描述器就是有“绑定行为”的对象属性,该属性的访问控制被描述器协议(__get__(),__set__(),__delete__())重写。如果对象定义了任一个上述方法,那它就是描述器。

描述器分两种,一种是只定义了__get__()方法的描述器叫非资料描述器(non-data descriptor),另一种是定义了__get__()和__set__()的描述器叫资料描述器(data descriptor)。

来个小例子吧

class DataDescriptor(object):

def __init__(self, val):

self.val = val

def __get__(self, instance, owner):

print "get method of %s" % self.val

return self.val

def __set__(self, instance, value):

print "set method of %s" % self.val

return self.val

class NonDataDescriptor(object):

def __init__(self, val):

self.val = val

def __get__(self, instance, owner):

print "get method of %s" % self.val

return self.val

class Context(object):

dd = DataDescriptor("data descriptor")

ndd = NonDataDescriptor("none data descriptor")

def __init__(self, val):

self.ndd = val

a = Context("haha~~")

a.dd

a.dd = "update"

a.dd

a.ndd

print(vars(Context))

print(vars(a))

print(a.ndd)

print(a.__dict__['ndd'])

print(type(a).__dict__['ndd'].__get__(a,Context))

#输出

get method of data descriptor

set method of data descriptor

get method of data descriptor

{'__module__': '__main__', 'dd': <__main__.datadescriptor object at>, 'ndd': <__main__.nondatadescriptor object at>, '__dict__': , '__weakref__': , '__doc__': None, '__init__': }

{'ndd': 'haha~~'}

haha~~

haha~~

get method of none data descriptor

none data descriptor

描述器的调用

为说明描述器的调用,先看了解一下属性的查找过程,属性查找的过程也就是调用的过程。

实例属性的查找过程

对于a = A(), a.attr会先调用实例的__getattribute__, 对描述器方法__get__的调用就发生在__getattribute__的内部,如果该方法抛出AttributeError异常,而且类A有定义__getattr__方法,这时__getattr__会被调用。如果将__getattribute__内部查找顺序考虑在内,则属性的默认查找顺序是这样的:

如果attr在A或其基类的__dict__中, 且attr是资料描述器, 那么调用其__get__方法, 否则

如果attr出现在a的__dict__中, 那么直接返回a.__dict__['attr'], 否则

如果attr出现在A或其基类的__dict__中

如果attr是非资料描述器,那么调用其__get__方法, 否则

返回__dict__['attr']

如果A有__getattr__方法,调用__getattr__方法,否则

抛出AttributeError

类属性的查找过程

考虑到python中类也是对象,类是元类(metaclass)的实例,这样对应之后,类属性的查过程与实例属性的查找过程就基本相同了,除了第二步。由于类可能有父类,所以这里第二步就变成了:

只要A.__dict__['attr']是一个descriptor,都调用其__get__方法,否则返回A.__dict__['attr']

这一步用python描述就是:

def __getattribute__(self, key):

"Emulate type_getattro() in Objects/typeobject.c"

v = object.__getattribute__(self, key)

if hasattr(v, '__get__'):

return v.__get__(None, self)

return v

正如上面说的,描述器是在__getattribute__内部调用,所以重写__getattribute__可以修改默认的描述器调用,甚至关闭描述器的调用。另外,描述器只在新式类(继承自object,python 2.2引入)可用。

应用

方法(method)

python面向对象的特征是建立在基于函数的环境之上的,非资料描述器就是两者之间的桥梁。函数中有__get__()方法以方便在属性访问时绑定方法,也就是说所有的函数都是非资料描述器。类的字典中将方法存成函数,类定义的时候,与函数的声明一样,方法使用def或lambda来声明。

在python中,类似a.b()这样的方法调用是分两步的:获取属性a.b和调用。第一步是个参数绑定的过程,返回一个可调用的bound method对象,如果不能进行进行参数绑定则返回unbound method对象,不可调用。

class Method(object):

name = "class name"

def __init__(self,name):

self.name = name

def instance_method(self):

return self.name

@classmethod

def class_method(cls):

return cls.name

@staticmethod

def static_method(x,y):

return x + y

实例方法(instancemethod)

print(Method.__dict__['instance_method'])

a = Method("haha~")

print(a.instance_method)

print(type(a).__dict__['instance_method'].__get__(a,type(a)))

print(Method.instance_method)

print(Method.__dict__['instance_method'].__get__(None,Method))

print(a.instance_method())

print(Method.instance_method(a))

#输出

>

>

haha~

haha~

实例方法调用时,需要有一个实例才能绑定,对于a.instance_method()这个实例就是函数定义时的self,对于Method.instance_method(a)这个实例就很明显了。

类方法

print(vars(a))

print(vars(Method))

print(a.class_method)

print(type(a).__dict__['class_method'].__get__(a,type(a)))

print(type(a).__dict__['class_method'].__get__(None,type(a)))

print(type(a).__dict__['class_method'].__get__(a,type(a))())

print(type(a).__dict__['class_method'].__get__(None,type(a))())

print(Method.class_method)

print(Method.__dict__['class_method'].__get__(None,Method))

print(Method.__dict__['class_method'].__get__(None,Method)())

#输出

{'name': 'haha~'}

{'__dict__': , '__module__': '__main__', 'static_method': , 'name': 'class name', 'instance_method': , '__weakref__': , 'class_method': , '__doc__': None, '__init__': }

>

>

>

class name

class name

>

>

class name

类方法绑定参数是类,实例并没有用。通过实例调用时,实例a中并没有class_method属性,按属性的查找过程,会到类里查找,并通过非资料描述器的__get__方法调用,通过Method调用时,参考前面的类属性查找过程。

静态方法

print(a.static_method)

print(type(a).__dict__['static_method'])

print(type(a).__dict__['static_method'].__get__(a,type(a)))

print(type(a).__dict__['static_method'].__get__(None,type(a)))

#输出

静态方法是一类特殊的方法,由定义可知,静态方法并不需要绑定到实例或类上,它只是一个普通的函数,调用时不再需要返回bound method对象。

待续

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值
>