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

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

  • 类属性与实例属性同名时,后者会遮蔽类属性:
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__方法的类则为非覆盖型描述符,无法完成实例属性遮蔽,且只读属性。

原文地址:https://www.cnblogs.com/ivan09/p/14189988.html