总是在代码里面看到,__set__,__get__,__getattr__,__setattr__,__getatribute__,property。
这些东东的通常作用基本是属性拦截,这里做个笔记记录下使用过程及差异化用途(所有都基于新式类)。
标题的那四类就是基本的属性管理方法
先介绍property的使用例子。
#!/usr/bin/env python
# coding=utf-8
class Powers(object):
def __init__(self, square, cube):
self._square = square
self._cube = cube
def getSquare(self):
return self._square ** 2
def setSquare(self, value):
self._square = value
square = property(getSquare, setSquare)
def getCube(self):
return self._cube ** 3
cube = property(getCube)
X = Powers(3, 4)
print X.square
print X.cube
输出:
>>>9
64
25
从输出的结果可以看到,square,cube已经做了操作,property的功能就是拦截属性并对其进行额外操作。
proprty(get, [set, del, doc)这个装饰起的函数格式是这样的,get用来拦截属性的获取,set拦截属性的赋值。返回的值即是对应绑定的属性名。这个函数也可以用装饰器表达,但是感觉不如函数来得容易理解。
如果用描述符来做这样一个事,先需要一个类去定义属性,也就是要两个描述类分别定义square和cube
#!/usr/bin/env python
# coding=utf-8
class DescSquare(object):
def __get__(self, instance, owner):
return instance._square ** 2
def __set__(self, instance, value):
instance._square = value
class DescCube(object):
def __get__(self, instance, owner):
return instance._cube ** 3
class Powers(object):
square = DescSquare()
cube = DescCube()
def __init__(self, square, cube):
self._square = square
self._cube = cube
X = Powers(3, 4)
print X.square
print X.cube
X.square = 5
print X.square
输出:
>>>>9
64
25
可以看到当实例化X = Powers(3, 4)后,对属性square和cube的访问以及被处理过了,这样的代码看起来更pythonic。
接下来看下__setattr__,__getattr__
#!/usr/bin/env python
# coding=utf-8
class Powers(object):
def __init__(self, square, cube):
self._square = square
self._cube = cube
def __getattr__(self, name):
if name == 'square':
return self._square ** 2
elif name == 'cube':
return self._cube ** 3
else:
raise TypeError('unknown attr:' + name)
def __setattr__(self, name, value):
if name == 'square':
self.__dict__['_square'] = value # 属性赋值是要用__dict__去代替self.square,因为后者会导致死循环,每一次赋值又会调用__setattr__
else:
self.__dict__[name] = value
X = Powers(3, 4)
print(X.square)
print(X.cube)
X.square = 5
print(X.square)
输出:
>>>>9
64
25
最后使用__getattribute__
#!/usr/bin/env python
# coding=utf-8
class Powers(object):
def __init__(self, square, cube):
self._square = square
self._cube = cube
def __getattribute__(self, name):
if name == 'square':
return object.__getattribute__(self, '_square') ** 2
elif name == 'cube':
return object.__getattribute__(self, '_cube') ** 3
else:
return object.__getattribute__(self, name)
def __setattr__(self, name, value):
if name == 'square':
self.__dict__['_square'] = value
else:
self.__dict__[name] = value
X = Powers(3, 4)
print(X.square)
print(X.cube)
X.square = 5
print(X.square)
输出:
>>>>9
64
25
上述四个例子殊途同归,比较下异常:
__getattribute__和_getattr__是有很大的相似性的,前者相对来说更神通广大一点,一般所有的属性访问都会先走__getattribute__,然后走__getattr__(如果定义的话),可以说getattr是用来针对特殊场景的一种补充(getattr拦截未定义的属性,getattribute拦截所有属性)
__get__撇开来说,每次访问描述符时都会调用,即访问定义__get__的类
举个栗子来总结以上。
#!/usr/bin/env python
# coding=utf-8
class Example(object):
eggs = 100
def __get__(self, instance, owner):
print "__get__ method! args: [{0}, {1}]".format(instance, owner)
return self
def __getattr__(self, attr):
print "__getattr__ method! args: {0}".format(attr)
return self
def __getattribute__(self, attr):
print "__getattribute__ method! args: {0}".format(attr)
return object.__getattribute__(self, attr)
class Instance(object):
desc = Example()
exp1.eggs # 访问存在属性,由下面输出可知只调用了getattribute
exp1.noeixst # 访问不存在属性,两者均调用了
exp2 = Instance() # 掉描述符实例
exp2.desc # 访问描述符属性,调用了get方法
exp2.desc.eggs # 访问描述符属性的属性,调用了get和getattribute,可以猜想到党访问属性的属性不存在时会调用getattr
输出:
>>>>__getattribute__ method! args: eggs
__getattribute__ method! args: noeixst
__getattr__ method! args: noeixst
__get__ method! args: [, ]
__get__ method! args: [, ]
__getattribute__ method! args: eggs
通过上述的栗子可以了解大概的拦截顺序。
再总结一下描述符和属性:
描述符和属性特性
描述符作用: 会改变一个属性的基本的获取、设置和删除方式。
访问方式: 访问一个实例的属性的时候是先遍历它和它的父类,寻找它们的__dict__里是否有同名的data descriptor如果有,就用这个data descriptor代理该属性,如果没有再寻找该实例自身的__dict__ ,如果有就返回。任然没有再查找它和它父类里的non-data descriptor,最后查找是否有__getattr__
描述符定义: __set__(任何特性被设置用). __get__(任何特性被读取时用). __del__(删除特性)拥有三者之一即可称为描述符, 这些方法在 __dict__前被调用
数据描述符:__get__, __set__
非数据描述符: __get__
只有当确实需要在访问属性的时候完成一些额外的处理任务时,才应该使用property。不然代码反而会变得更加啰嗦,而且这样会让程序变慢很多。
基本格式
class Descriptor:
"docstring goes here"
def __get__(self, instance, owner[宿主类]): …
def __set__(self, instance, value): …
def __delete__(self, instance): …
访问属性,被描述符拦截,property算低级别的描述符
非数据描述符:含__get__, 访问顺序低于__dict__
数据描述符:含__get__,set__, 用于有set控制输入,访问顺序会高于__dict__
def __getattr__(self, name):
def __getattribute__(self, name):
def __setattr__(self, name, value):
def __delattr__(self, name):
获取__dict__属性本身会再次触发__getattribute__,导致一个递归循
一篇介绍描述符的文章,写的通俗易懂:link