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)

<pre name="code" class="python">输出:
>>>>9
<pre name="code" class="javascript"><pre name="code" class="python">    64
    25 

 
 
 上述四个例子殊途同归,比较下异常: 

  1. __getattribute__和_getattr__是有很大的相似性的,前者相对来说更神通广大一点,一般所有的属性访问都会先走__getattribute__,然后走__getattr__(如果定义的话),可以说getattr是用来针对特殊场景的一种补充(getattr拦截未定义的属性,getattribute拦截所有属性)
  2. __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: [<__main__.Instance object at 0x7fbc06d03650>, <class '__main__.Instance'>]
    __get__ method! args: [<__main__.Instance object at 0x7efdc8584650>, <class '__main__.Instance'>]
    __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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值