• 从实例属性到特性、描述符


    实例属性与类属性、特定遮蔽关系

    • 类属性与实例属性同名时,后者会遮蔽类属性:
    class Demo():
        des = "Class Demo attribute value One"
        cash = "Class Demo attribute value Two"
    
        def __init__(self):
            self.des = "The instance attribute value"
    
    demo = Demo()
    print(demo.__dict__) # 返回一个字典,代表实例的属性集合:{'des': 'The instance attribute value'}
    print(demo.des)  # The instance attribute value
    print(Demo.des)  # Class Demo attribute value one
    print(demo.cash)  # Class Demo attribute value Two
    
    • 实例属性与特性重名时,实例属性会被遮蔽:
      • 特性函数:property(fget=None,fset=None,Fdel=None,doc=None)):
        class C:
            def __init__(self):
                   self._x=None
            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.") # 定义托管属性 x,同时将方法getx,setx,delx等三个类方法变为类属性操作
        c = C()
        print(c._x) # 返回None
        print(c.x)  # 返回None c.x会调用getx函数返回_x的值
        c.x =2      #set value 会调用setx函数
        print(c._x) #返回经过上一步赋值以后的value
        
      • 特性 @property装饰器:
        class C:
            def __init__(self):
                self._x = None
            @property
            def x(self): # 方法名均为x
                   """I'm the 'x' property."""
                   print("get value is running")
                   return self._x
            @x.setter
            def x(self, value): # 方法名均为x
                  print('set value is running')
                  self._x = value
            @x.deleter
            def x(self): # 方法名均为x
                   del self._x
        c = C()
        c.x = 2 # x已变为属性或者说特性,赋值操作会调用@x.setter
        print(c.x) # 同理 调用 get value
        
      • 特性 @property装饰器:
        class C():
            value = 'the class attribute value'
        
            @property
            def demo(self):
                return "the property attribute value"
        c  = C()
        print(c.value) # 打印出类属性
        print(c.__dict__) # {} 显示无实例属性
        c.value =20   # 给实例属性赋值,创建一个实例属性
        print(c.value) #20
        print(c.__dict__) #{'value': 20}
        print(C.value) #the class attribute value 显示类属性
        c.demo =2  #尝试给类特性赋值, 报错 AttributeError: can't set attribute 因为该类特性仅有get value 只读特性
        print(c.demo)  # the property attribute value 打印类特性
        c.__dict__['demo'] = 'instance attribute value' #虽然无法通过实例.特性 赋值,此办法可以通过
        print(c.demo) # the property attribute value ,特性依然覆盖实例属性
        C.demo = 1 # 销毁类特性
        print(c.demo) # instance attribute value C.demo不再是特性,无法覆盖实例属性
        

    特性工厂函数

    • 为了实现对属性存取设定一定逻辑,避免不合规的数据出现,利用特性会覆盖实例属性这一特点,在取值、存储值之前可以对数据进行条件判断:例如不允许出现negative or zero :
    # quantity() 作为特性工厂函数
    def quantity(storage_name): #getter以及setter闭包函数函数中 storage_name当作自由变量使用,会保存该变量的状态
    
        def getter(instance): # 托管的是LineItem类的类属性,对应的instance是LineItem的实例
            return instance.__dict__[storage_name]  # 此处使用 instance.storage_name 会报错 ,python解释器会认为是给实例创造名为storage_name 的属性
            #return  getattr(instance,storage_name) # 获得instance实例中名为storage_name变量中存储的值
        def setter(instance,value):
            if value>0:
                instance.__dict__[storage_name] = value # 此时添加实例属性。(特性管理的实际是实例属性)
                #setattr(instance,storage_name,value)   # 将instance实例中名为storage_name变量中存储的值 设置为value
            else:
                raise ValueError('value must be > 0')
        return property(getter,setter) # 返回property对象
    
    class LineItem():
        weight = quantity('weight')  # property instance
        price = quantity('price')   # property instance
        def __init__(self,doc,weight,price):
            self.doc = doc
            self.weight =weight  # 存在weight特性 会调用quantity函数中的setter函数 与类属性同名
            self.price = price #与类属性同名 否则不存在特性覆盖
    
        def subtotal(self):
            return self.weight*self.price  # 存在weight特性 会调用quantity函数中的getter函数
    
    demo = LineItem(10,20,30)
    print(demo.subtotal())
    
    • 在上述get()、set()函数中调用setattr(obj,name,value)以及getattr(obj,name),因为已提到过instance.属性(属性为特性)会被特性覆盖,以下将会导致get、set被递归调用,没有穷尽:

    描述符类

    • 描述符可以控制属性存取的逻辑,在上述特性工厂的基础上做进一步改进:
      • 描述符:言简意赅 实现了__set__、get、__delete__等方法的类
      • 实现:将描述符类的实例作为控制属性存取类的类属性
        class quantity():
              def __init__(self,storage_name):
                    self.storage_name =storage_name
      
              def __get__(self, instance, owner):
                    return instance.__dict__[self.storage_name] # 防止递归 仍不能使用getattr
      
              def __set__(self, instance, value):
                    if value>0:
                    # 防止递归 仍不能使用setattr
                         instance.__dict__[self.storage_name] =value  # self.__dict__[self.storage_name] = value self代表的是 LineItem类属性中创建的quantity实例
                   else:
                         raise ValueError('Value must > 0 ')
      
        class LineItem():
              weight = quantity('weight')  # property instance
              price = quantity('price')   # property instance
              
              def __init__(self,doc,weight,price):
                    self.doc = doc
                    self.weight =weight  # 存在weight特性 会调用quantity函数中的__set__
                    self.price = price
      
              def subtotal(self):
                    return self.weight*self.price  # 存在weight特性 会调用quantity函数中的__get__
      
      demo = LineItem(10,20,30)
      print(demo.subtotal())
      
      • 以下图表反映了在描述符类quantity方法中 self 与instance的代表着什么:self quantity描述类实例,instance代表LineItem托管类实例:

      • 在托管类LineItem中因为需要在创建描述符实例的时候填入对应的列属性对应的名称,以下做一个改进:

        class quantity():
              __counter = 0  # 作为类变量 为quantity所有实例共享
              def __init__(self):
                    cls= self.__class__
                    prefix = cls.__name__
                    index = cls.__counter
                    self.storage_name = '__{}_{}'.format(prefix,index)
                    cls.__counter +=1 #每创建一个quantity实例 计数器自增1
        
              def __get__(self, instance, owner):
                   return instance.__dict__[self.storage_name]  #可以使用getattr函数 因为托管的weight、price属性与存储的storage_name名称不同
        
              def __set__(self, instance, value):
                   if value>0:
                          instance.__dict__[self.storage_name] =value  #可以使用setattr函数 因为托管的weight、price属性与存储的storage_name名称不同
                   else:
                         raise ValueError('value must be > 0')
        
        class LineItem():
              weight = quantity()  # property instance
              price = quantity()   # property instance
             def __init__(self,doc,weight,price):
                   self.doc = doc
                   self.weight =weight
                   self.price = price
        
              def subtotal(self):
                    return self.weight*self.price
        demo = LineItem(10,20,30)
        print(demo.subtotal())
        
        
      • 特性工厂函数同步改进:

        def quantity():
            try:
                  quantity._counter +=1
            except AttributeError:
                  quantity._counter =0
        
            storage_name = '__{}#{}'.format('quantity',quantity._counter)
        
            def getter(instance):
                  return getattr(instance,storage_name)
        
            def setter(instance, value):
                  if value>0:
                       setattr(instance,storage_name,value)
                 else:
                       raise ValueError('value must be > 0')
           return property(getter,setter)
        
        class LineItem():
              weight = quantity()  # property instance
              price = quantity()   # property instance
              def __init__(self,doc,weight,price):
                    self.doc = doc
                    self.weight =weight
                    self.price = price
        
             def subtotal(self):
                   return self.weight*self.price
        
        demo = LineItem(10,20,30)
        

    上述例子中实现了__set__方法的描述符 可以遮盖实例特性,成为覆盖型描述符,只实现了__get__方法的类则为非覆盖型描述符,无法完成实例属性遮蔽,且只读属性。

  • 相关阅读:
    机器学习入门之二:一个故事说明什么是机器学习(转载)
    机器学习入门之一:背景介绍(转载)
    python 读取CSV文件 中文乱码
    如何快速学习一门新技术(转载)
    Oracle12c多租户如何启动关闭CDB或PDB (PDB自动启动)
    oracle单实例12.2.0.1安装
    PRVF-0002 : could not retrieve local node name
    图形化升级单机oracle 11.2.0.4 到 12.2.0.1
    ORA-00845: MEMORY_TARGET not supported on this system
    行转列、列转行
  • 原文地址:https://www.cnblogs.com/ivan09/p/14189988.html
Copyright © 2020-2023  润新知