python 内置属性__setattr___python 属性四种管理方法property,descriptor, __get/setattr__, __getatribute __...

总是在代码里面看到,__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

你可能感兴趣的:(python,内置属性__setattr__)