• Python笔记(4)类__属性与描述符


    部分参考自:http://www.geekfan.net/7862/

    新式类与经典类

    2和3不一样,3都是新式类。

    新式类和经典类的区别:

    class A:
        #classic class
        """this is class A"""
        pass
        __slots__=('x','y')
        def test(self):
            # classic class test
            """this is A.test()"""
            print "A class"
    class B(object):
        #new class
        """this is class B"""
        __slots__=('x','y')
        pass
        def test(self):
            # new class test
            """this is B.test()"""
            print "B class"
    
            
    if __name__ == '__main__':
        a=A()
        b=B()
        print dir(a)
        print dir(b)
    
    ['__doc__', '__module__', '__slots__', 'test']
    ['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', 'test', 'x', 'y']
    

      新式类要指明父类,上面代码class B 声明他的父类为object。

    python是动态语言,可以动态的添加属性。

    >>> a.x = 1
    >>> a
    <__main__.A instance at 0x05BBB620>
    >>> a.x
    1
    

     __slots__槽,属性限制了实例b只能添加x,y属性,a是经典类,可以继续添加,但是b是新式类不能继续添加。

    >>> a.z = 2
    >>> a.z
    2
    >>> b.z = 2
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: 'B' object has no attribute 'z'
    

      

    >>> help(a)
    Help on instance of A in module __main__:
    
    class A
     |  this is class A
     |  
     |  Methods defined here:
     |  
     |  test(self)
     |      this is A.test()
    
    >>> help(b)
    Help on B in module __main__ object:
    
    class B(__builtin__.object)
     |  this is class B
     |  
     |  Methods defined here:
     |  
     |  test(self)
     |      this is B.test()
     |  
     |  ----------------------------------------------------------------------
     |  Data descriptors defined here:
     |  
     |  x
     |  
     |  y
    

      B类由于是新式类 __slots__起作用了,尽量使用新式类,因为这样python2,3中都能跑。

    属性和封装

    实例类型

    __init__  双下划线是特殊方法,__init__定义实例属性,owner,country是实例属性,country是类属性。

    调用的时候,比如类属性和实例属性名字一样,调用实例属性。如果没有实例属性,则去寻找是不是存在类属性。

    class Car(object):
        country = u'中国' 
        def __init__(self,owner=None):
            self.owner = owner
            self.country = "china"
    
    if __name__ == '__main__':
        a = Car(u'小张')
        print a.country
        
        a.country = u'美国'    
        print a.country
        print "--------------------------"
        del a.country
        print a.country
    
    >>> china
    美国
    --------------------------
    中国
    
    私有属性

    私有属性只在函数内部可见。通过get,set方法对其赋值更改。

    在变量前加两个下划线__ 可以间接访问,只加一个下划线_模块私有化。变量前后各两个下划线__是系统自带的属性。

    class Car(object):
        def __init__(self,owner=None):
            self.__owner = owner
    
        def getOwner(self):
            return self.__owner
        def setOwner(self, value):
            self.__owner = value
    
    if __name__ == '__main__':
        a = Car(u'黑板客')
        print a.getOwner()
    

      

    >>> a.owner
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: 'Car' object has no attribute 'owner'
    >>> a.getOwner()
    u'u9ed1u677fu5ba2'
    >>> dir(a)
    ['_Car__owner', '__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'getOwner', 'setOwner']
    >>> a._Car__owner
    u'u9ed1u677fu5ba2'
    

      

    描述符

    装饰器描述符

    @property @xx.setter @xx.deleter

    用@property装饰器指定一个getter方法,用@owner.setter装饰器指定了一个setter方法。当我们这么做的时候,访问owner属性,python就会自动调用相应的getter/setter方法。这样当我们要判断一个值的时候,如果放到__init__里,他只能在出初始化的时候判断,而放到setter里,每次set的时候都会判断。

    可以把get,set方法变成属性访问。

    class Car(object):
        def __init__(self,owner=None):
            self._owner = owner
     
        @property
        def owner(self):
            return self._owner
        @owner.setter
        def owner(self, value):
            self._owner = value
        @owner.deleter
        def owner(self):
            self._owner = None
        
    if __name__ == '__main__':
        a = Car(u'你大爷')
        print a.owner
        del a.owner
        print a.owner 
    你大爷
    None
    

     这样一个owner get,set,del要定义三个,如果有别的属性,则又需要三个,这样会产生冗余,重复代码。

    __getattr__, __setattr__, __delattr__

    __getattr__  在变量的__dict__和_class__.__dict__中没有找到属性,就会调用__getattr__,如果有的话,就直接调用__dict__中的值了。

    __setattr__ 变量赋值

    __delattr__ 删除变量

    class Car(object):
        country = u'中国'
        #__slots__=('length','width','height','owner','__dict__')
        
        def __init__(self, length, width, height, owner=None):
            self.owner = owner
            self.length = length
            self.width = width
            self.height = height
            
        def __getattr__(self,name):
            print "__getattr__",name
            return self.__dict__.get(name,None)
            
        def __setattr__(self,name,value):
            print "__setattr__",name
            if name!='owner':
                assert value>0, name+" must larger than 0"
            self.__dict__[name]=value
            
        def __delattr__(self,name):
            print "__delattr__",name
            if name=='owner':
                self.__dict__[name]=None
    
                
    if __name__ == '__main__':
        a = Car(1.2,1.4,1.5,u'二大爷')
    

    输出:  

    __setattr__ owner
    __setattr__ length
    __setattr__ width
    __setattr__ height
    

     把__slots__加上之后,因为可以访问__setattr__所以还是可以任意的加属性而不会报错,要使得slots有效果,得在__setattar__里面修改代码:

        def __getattr__(self,name):
            print "__getattr__",name
            assert name in self.__slots__, "Not have this attribute "+name
            return self.__dict__.get(name,None)
            
        def __setattr__(self,name,value):
            print "__setattr__",name
            assert name in self.__slots__, "Not have this attribute "+name        
            if name!='owner':
                assert value>0, name+" must larger than 0"
            self.__dict__[name]=value
            
        def __delattr__(self,name):
            print "__delattr__",name
            assert name in self.__slots__, "Not have this attribute "+name
            if name=='owner':
    
    类描述符

      描述符可以用作类的属性,数据描述符__get__,__set__,__del__。

    class PositiveNum(object):
        def __init__(self):
            self.default = 1
            self.data = {}
     
        def __get__(self, instance, owner):
            # instance = x
            # owner = type(x)
            print "__get__",instance,owner
            return self.data.get(instance, self.default)
     
        def __set__(self, instance, value):
            # instance = x
            print "__set__",instance,value
            try:
                assert int(value)>0
                self.data[instance] = value
            except AssertionError:
                print "ERROR: "+str(value)+" is not positive number."
            except:
                print "ERROR: "+str(value)+" is not number value."
                
        def __delete__(self,instance):
            print "__delete__",instance
            del self.data[instance]
            
    class Car(object):
        country = u'中国'
        length = PositiveNum()
        width = PositiveNum()
        height = PositiveNum()
        __slots__=('owner','length','width','height')
        
        def __init__(self, length, width, height, owner=None):
            self.owner = owner
            self.length = length
            self.width = width
            self.height = height
            
    
    if __name__ == '__main__':
        a = Car(1.2,1.4,1.5,u'黑板客')
        b = Car(2.2,2.4,2.5,u'小明')
        print a.length
        a.length=1
    

      当解释器遇到print a.length时,它会把length当作一个带有__get__方法的描述符,调用a.length.__get__方法并将方法的返回值打印,这和上面的property相似。__get__接收两个参数:instance 实例对象,这里就是a.length中的a,另一个是实例的类型Car。在一些文档中,Car被称作描述符的所有者(owner)。如果需要访问Car.length,python将会调用Car.length.__get__(None,Car)。可以看到第一个参数要么是实例,要么是None。

      当解释器看到a.length = 1时,Python识别出length是一个带__set__方法的描述符,于是就调用a.length.__set__(a,100),第一个参数instance是实例,第二个是赋值。

      删除时Car.length.__delete__(a)。

      每个PositiveNum维护着一个字典,其中保存着所有者实例和对应数据的映射关系。调用a.length时,__get__方法会找出与a相关的数据,并发挥结果,如果不存在就返回一个默认值。__set__采用的方式相同,但是会包含额外的非法检查。

      描述符作用与类的层次上,每一个类的实例都共享同一个描述符。所以不同的实例对象不得不手动的管理不同的状态,需要显示的将参数精确的传递给__get__,__set__以及__delete__方法。

      如果将PositiveNum中的 data = {}去掉,由于描述符是基于类层面的,他们会共享同一个类属性,这就是使用字典的原因。__get__,__set__参数也指明哪一个实例,以实例为字典的key。

    错误示例:

    class PositiveNum(object):
        def __init__(self,value):
            self.val = value
     
        def __get__(self, instance, owner):
            # instance = a,b
            # owner = Car
            print "__get__",instance,owner
            return self.val
     
        def __set__(self, instance, value):
            # instance = a,b
            print "__set__",instance,value
            try:
                assert int(value)>0
                self.val = value
            except AssertionError:
                print "ERROR: "+str(value)+" is not positive number."
            except:
                print "ERROR: "+str(value)+" is not number value."  
                
        def __delete__(self,instance):
            print "__delete__",instance
            self.val = None
    
        #def __getattribute__(self,name):
            #print self, name
            
    class Car(object):
        country = u'中国'
        length = PositiveNum(0)
        width = PositiveNum(0)
        height = PositiveNum(0)
        #__slots__=('owner','length','width','height')
        
        def __init__(self, length, width, height, owner=None):
            self.owner = owner
            self.length = length
            self.width = width
            self.height = height
            
    
    
    if __name__ == '__main__':
        a = Car(1.2,1.4,1.5,u'黑板客')
        b = Car(2.2,2.4,2.5,u'小明')
    

      

    a.length
    __get__ <__main__.Car object at 0x098E61B0> <class '__main__.Car'>
    Out[39]: 2.2
    
    b.length
    __get__ <__main__.Car object at 0x098E6230> <class '__main__.Car'>
    Out[40]: 2.2
    

     虽然a定义的1.2,但由于与b公用一个类属性,所以也变成了2.2。

    __getter__,__setter__和类描述符都可以去掉重复的臃肿,实现内部代码的简洁。

  • 相关阅读:
    PHP分页类
    phpexcel 控制导出数据内容和行数
    phpexcel 导入 和 导出
    PHP无限极分类
    php 对查询结果集进行排序
    php 删除文件夹及文件夹内文件函数
    php 字符串截取函数
    php 获取用户登录的ip
    js layer页面层加载新网站
    分享到qq
  • 原文地址:https://www.cnblogs.com/zephyr-1/p/5665525.html
Copyright © 2020-2023  润新知