python描述符和属性查找
python描述符
定义
一般说来,描述符是一种访问对象属性时候的绑定行为,如果这个对象属性定义了__get__()
,__set__()
, and __delete__()
一种或者几种,那么就称之为描述符。描述符在属性查找的时候可以覆盖默认的属性查找行为。
如果一个对象定义了__get__()
和__set__()
方法,那么称之为数据描述符,如果只定义了__get__()
称之为非数据描述符。
描述符调用
描述符可以直接通过方法名调用,如d.__get__(obj)
。也可以通过属性查找调用,如:obj.d
,如果d定义了__get__()
,那么就会调用d.__get__(obj)
描述符的调用实现定义在object.__getattribute__()
方法中:
- 对于实例对象
b
,将b.x
转为type(b).__dict__['x'].__get__(b, type(b))
- 对于类
b
,将b.x
转为b.__dict__['x'].__get__(none, b)
__getattribute__()
用纯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__()
方法可以屏蔽描述符的调用 - 描述符只能在新式类(即objects的子类)中才会起作用
-
object.__getattribute__()
与type.__getattribute__()
调用__get__()
过程不同 - 属性查找时数据描述符总是覆盖实例属性(具体在中解释)
- 非数据描述符可能被实例属性覆盖
实例属性指的是对于实例b,存在于b.__dict__中的属性。b.__dict__只包含有实例特有的属性,不包含类共有的属性。
描述符应用
properties
property可以将一个类的方法构建成数据描述符,从而可以使得以类的属性调用的方式调用方法。
例如:
class c(object): def getx(self): return self.__x def setx(self, value): self.__x = value def delx(self): del self.__x x = property(getx, setx, delx, "i'm the 'x' property."
从而调用c.x
隐式调用c.getx()
如果property用纯python写,那么等价于:
class property(object): "emulate pyproperty_type() in objects/descrobject.c" def __init__(self, fget=none, fset=none, fdel=none, doc=none): self.fget = fget self.fset = fset self.fdel = fdel if doc is none and fget is not none: doc = fget.__doc__ self.__doc__ = doc def __get__(self, obj, objtype=none): if obj is none: return self 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") self.fset(obj, value) def __delete__(self, obj): if self.fdel is none: raise attributeerror("can't delete attribute") 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__)
函数和方法
在python中,一切皆是对象。python的方法也不例外,所有python函数均是function
这个类的实例,function
定义了__get__()
方法(使用type(方法).__dict__可以得到__get__
这个属性),也就是所有函数均为非数据描述符。根据是实例调用还是类调用,描述符返回绑定方法和非绑定方法。
>>> class d(object): ... def f(self, x): ... return x ... >>> d = d() >>> d.__dict__['f'] # stored internally as a function <function f at 0x00c45070> >>> d.f # get from a class becomes an unbound method <unbound method d.f> >>> d.f # get from an instance becomes a bound method <bound method d.f of <__main__.d object at 0x00b18c90>>
绑定方法:实例对象调用方法时候会传出一个self参数,将这个实例对象与方法绑定。
非绑定方法:类直接调用方法属性
将function类用纯python写,等价如下:
class function(object): . . . def __get__(self, obj, objtype=none): "simulate func_descr_get() in objects/funcobject.c" return types.methodtype(self, obj, objtype)
type.methodtype()
手动创建一个绑定方法来使用。只有当实例被使用的时候绑定方法才会被创建.
static method类和class method类
两者都是使用非数据描述符的方式构建描述符。
若使用纯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
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
python属性查找
如果有obj = clz()
, 那么obj.attr
查找顺序如下:
- 调用
__getattribute_
- 如果
attr
是出现在clz或其基类的__dict__中,且attr是data descriptor, 那么调用其__get__
方法 - 如果
attr
出现在obj的__dict__中, 那么直接返回obj.__dict__['attr']
- 如果
attr
出现在clz或其基类的__dict__中
4.1 如果attr是non-data descriptor,那么调用其__get__
方法
4.2 返回__dict__['attr']
- 如果clz有__getattr__方法,调用__getattr__方法,否则
- 抛出attributeerror
属性查找中__getattribute_
方法是无论如何都会被调用的,且从python描述符调用章节可知__getattribute_
实际上实现了描述符的调用。如果类同时定义了__getattr__
方法,只有在__getattribute_
显式调用__getattr__
或者__getattribute_
抛出attributeerror才会调用__getattr__
。
注意如果要重载
__getattribute_
方法,需要显式调用object.__getattribute__(self, name)
以防止无限循环.
例如:
class test(object): t = 5 def __getattribute__(self, item): if item=='t': return super(test, self).__getattribute__(item) return 6 t = test() print t.t print t.s 结果为: 5 6
python属性赋值
python通过对象的__setattr__
方法赋值,也可以通过setattr
方法,事实上setattr
就是通过调用__setattr__
完成赋值。属性赋值也会受到数据描述符影响。
举例来说:x.b=3
,属性赋值过程如下:
- 如果类重载了
__setattr__
方法,优先调用__setattr__
- 如果没有重载
__setattr__
方法,且b
是数据描述符,就会调用b.__set__()
- 如果以上都没有,调用
obj.__dict__['b'] = 3
(__setattr__
方法的默认行为)
推测跟
object.__getattribute__()
一样,object.__setattr__()
中实现了数据描述符的调用。
参考资料
上一篇: 设计模式——模板方法模式