python 描述器 详解_Python技术进阶——描述器

前言

在Python开发中,我们很少能接触到描述器的直接使用,但对于熟练使用Python的开发者,了解Python描述器的工作原理,能让你更深入地了解Python以及其设计的优雅之处。

其实我们开发中遇到的很多例子,例如:

装饰器property、staticmethod、classmethod

function、bound method、unbound method

是不是都很熟悉,其实这些都与描述器有着千丝万缕的关系,这篇文章就为大家一一解答其中的奥秘。

什么是描述器?

一般来说,描述器是一个有「绑定行为」的对象属性,它的访问控制被描述器协议方法重写。

回忆一下,在编程中我们说「行为」一般指的是方法。

那么这就容易理解了,「绑定行为」的对象属性,就是指这个「对象属性」依托于了另外的对象,这个对象里包含了很多「方法」来控制这个行为。

描述器协议

对象属性依托的对象中,包含的「方法」不能随便定义,而是规定好的,实现这些方法,就是实现了描述器协议,具体有以下几个:

get

set

delete

只要实现以上方法其一,这个对象就叫做描述器。

定义了get和set的对象叫做资料描述器

只定义了__get___的对象叫做非资料描述器

这两者有什么区别呢?我们下面再做解释。

# 描述器的调用

我们来看一段描述器的代码:

class Age(object):

"""这个类实现了描述器协议"""

def __init__(self, value=20):

self.value = value

def __get__(self, obj, type=None):

print 'get --> obj: %s type: %s' % (obj, type)

return self.value

def __set__(self, obj, value):

print 'set --> obj: %s value: %s' % (obj, value)

self.value = value

class Person(object):

age = Age() # 这个属性通过描述器托管给了另一个类

def __init__(self, name):

self.name = name

person = Person('zhangsan')

print person.age

# get --> obj: <__main__.Person object at 0x105e815d0> type:

# 20

print Person.age

# get --> obj: None type:

# 20

person.age = 25

# set --> obj: <__main__.Person object at 0x105e815d0> value: 25

print person.age

# get --> obj: <__main__.Person object at 0x105e815d0> type:

# 25

我们从代码看出,Person类的类属性age被Age实现,Age类实现了set和get方法,对于Person类来说,age就是一个描述器。

我们通过输出结果看出,当调用age属性时,都调用了Age的get方法,但打印出的参数结果不同:

当调用方是实例时,obj是Person实例,type是type(Person)

当调用方是类时,obj是None,type是type(Person)

工作原理

这背后的机制到底是怎样的呢?

要解释这个现象,我们要先从属性被访问的机制来说,调用a.b会发生什么?

如果a的类是继承了object,也就是说这个类是新式类,那么a.b会调用getattribute方法,如果类中没有定义这个方法,那么默认会调用object的getattribute方法。

而object在getattribute中就默认调用了描述器,但调用细节取决于a是一个类还是一个实例:

如果a是一个实例,object的这个方法会把其变为:

type(a).__dict__['b'].__get__(a, type(a))

如果a是一个类,object的这个方法会把其变为:

a.__dict__['b'].__get__(None, a)

所以我们就能看到上面例子输出的结果。

也就是说,描述器的调用入口,取决于getattribute!

如果我们重写了getattribute,那么会阻止描述器的调用。

方法就是描述器

我们思考一个问题,当一个类中的实例变量名与一个方法同名时,例如类A有一个实例变量和方法都叫foo,那么A().foo会输出实例属性还是调用方法?

class A(object):

def __init__(self):

self.foo = 'abc'

def foo(self):

return 'xyz'

print A().foo # abc

我们看到,A().foo输出了abc,也就是实例属性的值,而不是调用这个方法,这是怎么回事?

我们执行如下代码:

print dir(A.foo)

# ['__call__', '__class__', '__get__', '__delattr__', '__doc__', ...

看到了吗?dir(A.foo)包含了get方法,我们在上面知道描述器的定义是:只要实现了get、set、del其一,这个对象就是描述器。

也就是说:方法就是一个描述器,而且是一个非资料描述器。

其实在调用一个属性时,具体的执行顺序是这样的:

__getattribute()__ -> 资料描述器 > 实例变量 > 非资料描述器 > __getattr()__

当一个类中的实例变量名与一个方法同名时:

如果描述器是资料描述器,优先使用资料描述器

如果描述器是非资料描述器,优先使用字典中的属性

由于每个方法都是一个非资料描述器,所以优先使用实例变量。

到这里我们可以总结一下:

getattribute只对新式类的实例有用

描述器的调用是因为object的getattribute

重写getattribute方法会阻止正常描述器的调用

方法都是非资料描述器

实例和类调用get结果不一样

资料描述器 > 实例变量 > 非资料描述器调用

function/unbound method/bound method

我们常见的function、unbound method、bound method有什么区别呢?

class A(object):

def foo(self):

return 'xyz'

print A.__dict__['foo'] #

print A.foo #

print A().foo # >

它们的区别如下:

function就是一个函数,因为其实现了get,因此每个函数都是一个非资料描述器

类的字典把方法当做函数存储

当方法被实例调用时,返回绑定的方法(bound method)

当方法被类调用时,返回非绑定的方法(unbound method)

property/staticmethod/classmethod

Python把一些使用特别普遍的功能打包成了独立的函数,例如property、staticmethod、classmethod,这些方法都是基于描述器协议实现的。

property的Python版实现:

class property(object):

def __init__(self, fget=None, fset=None, fdel=None, doc=None):

self.fget = fget

self.fset = fset

self.fdel = fdel

self.__doc__ = doc

def __get__(self, obj, objtype=None):

if obj is None:

return self.fget

if self.fget is None:

raise AttributeError(), "unreadable attribute"

return self.fget(obj)

def __set__(self, obj, value):

if self.fset is None:

raise AttributeError, "can't set attribute"

return self.fset(obj, value)

def __delete__(self, obj):

if self.fdel is None:

raise AttributeError, "can't delete attribute"

return self.fdel(obj)

def getter(self, fget):

return type(self)(fget, self.fset, self.fdel, self.__doc__)

def setter(self, fset):

return type(self)(self.fget, fset, self.fdel, self.__doc__)

def deleter(self, fdel):

return type(self)(self.fget, self.fset, fdel, self.__doc__)

staticmethod的Python版实现:

class staticmethod(object):

def __init__(self, func):

self.func = func

def __get__(self, obj, objtype=None):

return self.func

classmethod的Python版实现:

class classmethod(object):

def __init__(self, func):

self.func = func

def __get__(self, obj, klass=None):

if klass is None:

klass = type(obj)

def newfunc(*args):

return self.func(klass, *args)

return newfunc

由此可见,通过描述符我们可以实现强大而灵活的属性和方法管理,但强大也意味着责任大,在合适的场景使用才能起到最佳的效果。

如果此文章能给您带来小小的工作效率提升,不妨小额赞助我一下,以鼓励我写出更好的文章!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值