• python面对对象编程-------5:获取属性的四种办法:@property, __setattr__(__getattr__) ,descriptor


    一:最基本的属性操作

     1 class Generic:
     2     pass
     3 
     4 g= Generic()
     5 
     6 >>> g.attribute= "value"    #创建属性并赋值
     7 >>> g.attribute
     8 'value'
     9 >>> g.unset
    10 Traceback (most recent call last):
    11 File "<stdin>", line 1, in <module>
    12 AttributeError: 'Generic' object has no attribute 'unset'
    13 >>> del g.attribute         #注意,此地时直接删除了这个属性
    14 >>> g.attribute
    15 Traceback (most recent call last):
    16 File "<stdin>", line 1, in <module>
    17 AttributeError: 'Generic' object has no attribute 'attribute'
    基本的属性操作

    二:@property

      被@property修饰的是一个方法,但此方法名可以像属性一样被获取,设置,删除
      需要注意的是,属性的外部添加是十分简单的,但property的外部添加不是,所以其与属性还是有区别的
      有两种方法创建property:
       1:用@property修饰的函数
       2:用property()方法
      有两种设计模式:
       1:懒惰计算模式:被调用时才执行
       2:主动计算模式:实例化时就执行

      懒惰模式:计算手牌总和
     1 class Hand_Lazy(Hand):
     2     def __init__( self, dealer_card, *cards ):
     3         self.dealer_card= dealer_card
     4         self._cards= list(cards)
     5     @property
     6     def total( self ):
     7         delta_soft = max(c.soft-c.hard for c in self._cards)
     8         hard_total = sum(c.hard for c in self._cards)
     9         if hard_total+delta_soft <= 21:
    10             return hard_total+delta_soft
    11         return hard_total
    12     @property
    13     def card( self ):
    14         return self._cards
    15     @card.setter
    16     def card( self, aCard ):
    17         self._cards.append( aCard )
    18     @card.deleter
    19     def card( self ):
    20         self._cards.pop(-1)
    21 
    22 
    23 d= Deck()
    24 h= Hand_Lazy( d.pop(), d.pop(), d.pop() )
    25 h.total                 #被调用时才执行计算手头的牌之和
    26 # 19
    27 h.card = d.pop()        #注意,可以将@property看作@property.getter,而此地可以看作两步,左边为@property获取属性,=号调用@property.setter并且将右边的d.pop()当作参数传入。
    28 h.total
    29 # 29
    懒惰模式

      主动计算模式

     1 # 将计算嵌入到@card.setter中,每新添加一张手牌就立马更新手牌总和
     2 class Hand_Eager(Hand):
     3     def __init__( self, dealer_card, *cards ):
     4         self.dealer_card= dealer_card
     5         self.total= 0
     6         self._delta_soft= 0
     7         self._hard_total= 0
     8         self._cards= list()
     9         for c in cards:
    10             self.card = c
    11     @property
    12     def card( self ):
    13         return self._cards
    14     @card.setter
    15     def card( self, aCard ):
    16         self._cards.append(aCard)
    17         self._delta_soft = max(aCard.soft-aCard.hard,self._delta_soft)
    18         self._hard_total += aCard.hard
    19         self._set_total()
    20     @card.deleter
    21     def card( self ):
    22         removed= self._cards.pop(-1)
    23         self._hard_total -= removed.hard
    24         # Issue: was this the only ace?
    25         self._delta_soft = max( c.soft-c.hard for c in self._cards)
    26         self._set_total()
    27 
    28     def _set_total( self ):
    29         if self._hard_total+self._delta_soft <= 21:
    30             self.total= self._hard_total+self._delta_soft
    31         else:
    32             self.total= self._hard_total
    33 
    34 d= Deck()
    35 h1= Hand_Lazy( d.pop(), d.pop(), d.pop() )
    36 print( h1.total )
    37 h2= Hand_Eager( d.pop(), d.pop(), d.pop() )
    38 print( h2.total )
    主动计算模式

      其实@property已经模糊了数据和行为了,那么到底什么时候我们需要使用@property呢?

        1:需要使用类中其他属性计算得到【也就是上面的情况】  

        2:对于难以查找或者计算的东西,将这个值以私有属性的形式缓存到本地,而后再次访问就快捷很多:

     1 from urllib.request import urlopen
     2 class WebPage:
     3     def __init__(self,url):
     4         self.url = url
     5         self._content = None
     6 
     7     @property
     8     def content(self):
     9         if not self._content:
    10             print("retriving new page")
    11             self._content = urlopen(self.url).read()
    12 
    13         return self._content
    14 
    15 import time
    16 webpage = WebPage("http://ccphillips.net/")
    17 now = time.time()
    18 content1 = webpage.content
    19 print(time.time()-now)
    20 now = time.time()
    21 content2 = webpage.content
    22 print(time.time()-now)
    23 
    24 输出:
    25 retriving new page
    26 14.51249384880066
    27 0.0        #!!!!
    用于缓存内容

      补充:廖雪峰的关于@property片段的代码

     1 class Student:
     2     def get_score(self):
     3         return self._score
     4 
     5     def set_score(self,value):
     6         if not isinstance(value,int):
     7             raise ValueError('must be integer')
     8         if value < 0 or value > 100:
     9             raise ValueError('0~100')
    10         self._score = value
    11 
    12 s=Student()
    13 s.set_score(60)
    14 s.get_score()
    15 # 60
    16 
    17 # 用@property优化:
    18 # 注意,可以把@property看作getter,而setter与deletter都是基于getter的
    19 class Student:
    20     @property
    21     def score(self):
    22         return self._score
    23 
    24     @score.setter
    25     def score(self,value):
    26         if not isinstance(value,int):
    27             raise ValueError('must be integer')
    28         if value < 0 or value > 100:
    29             raise ValueError('0~100')
    30         self._score = value
    31 
    32 s=Student()
    33 s.score = 60
    34 s.score
    35 # 60
    廖雪峰@property

    三:属性获取的特殊方法

      __getattr__(), __setattr__(), and __delattr__(),__dir__(),__getattribute__()
    __setattr__(): 创建属性并赋值
    __getattr__(): 首先:如果此属性已有值,不会用到__getattr__(),直接返回值就是了。
      其次:如果此属性没有值,此时调用__getattr__()并且返回其中设定的返回值。
      最后:如果压根没有这属性,报 AttributeError 错误。
    __delattr__():删除一个属性
    __dir__(): 返回包含属性的list
    __getattribute__():更加底层的属性获取方法,他默认从__dict__(或__slots__)中获取值,如果没有找到,调用__getattr__()作为反馈。如果发现此值是一个dexcriptor,就调用descriptor,否者就直接返回值。
    
    
        __getattr__()方法只当某个属性没有值时才起作用。

      
      1:创建immutable object
        什么是immutable object:不能够在外部直接赋值一个已有属性的值,不能创建新属性
        immutable object的一个特点是__hash__()能够返回固定的值
        版本一:用__slots__创建immutable object:
     1 class BlackJackCard:
     2     """Abstract Superclass"""
     3     __slots__ = ( 'rank', 'suit', 'hard', 'soft' )          #__slots__限定了只有这些属性可用
     4     def __init__( self, rank, suit, hard, soft ):
     5         super().__setattr__( 'rank', rank )
     6         super().__setattr__( 'suit', suit )
     7         super().__setattr__( 'hard', hard )
     8         super().__setattr__( 'soft', soft )
     9     def __str__( self ):
    10         return "{0.rank}{0.suit}".format( self )
    11     def __setattr__( self, name, value ):
    12         raise AttributeError( "'{__class__.__name__}' has no attribute '{name}'".format( __class__= self.__class__, name= name ) )
    13 
    14 # We defined __setattr__() to raise an exception rather than do anything useful.
    15 # __init__() use the superclass version of __setattr__() so that values can be properly set in spite of the absence of a working __setattr__() method in this class.
    16 # 我们知道,python并不阻止人干坏事,所以可以通过 object.__setattr__(c, 'bad', 5) 来绕过immutable机制
    __slots__创建immutable object

        版本2: 我们还可以通过继承 tuple 并且覆盖__getattr__()来写immutable object。

     1 class BlackJackCard2( tuple ):
     2     def __new__( cls, rank, suit, hard, soft ):          # tuple(iterable) -> tuple initialized from iterable's items
     3         return super().__new__( cls, (rank, suit, hard, soft) )
     4 
     5     def __getattr__( self, name ):      #translate __getattr__(name) requests to self[index] requests
     6         return self[{'rank':0, 'suit':1, 'hard':2 , 'soft':3}[name]]
     7 
     8     def __setattr__( self, name, value ):
     9         raise AttributeError
    10 
    11 >>> d = BlackJackCard2( 'A', '?', 1, 11 )
    12 >>> d.rank
    13 'A'
    14 >>> d.suit
    15 '?'
    16 >>> d.bad= 2            #不能改变属性值了
    17 Traceback (most recent call last):
    18 File "<stdin>", line 1, in <module>
    19 File "<stdin>", line 7, in __setattr__AttributeError
    继承tuple实现immutable object
      # 注意上面两个版本是有区别的,在版本2中可以通过d.__dict__来增加属性
      # 而版本1中用了__slots__后就会关闭__dict__
      
      2:创建一个一旦给定速度与时间就自动更新距离的类,让其继承自dict,好处是用format函数特别方便
     1 class RateTimeDistance( dict ):
     2     def __init__( self, *args, **kw ):
     3         super().__init__( *args, **kw )
     4         self._solve()
     5     def __getattr__( self, name ):
     6         return self.get(name,None)              #对应字典的get方法
     7     def __setattr__( self, name, value ):
     8         self[name]= value                       #对应字典的赋值方法
     9         self._solve()                           #在__setattr__中调用方法既是一旦赋值就能能够完成计算
    10     def __dir__( self ):
    11         return list(self.keys())
    12     def _solve(self):
    13         if self.rate is not None and self.time is not None:
    14             self['distance'] = self.rate*self.time
    15         elif self.rate is not None and self.distance is not None:
    16             self['time'] = self.distance / self.rate
    17         elif self.time is not None and self.distance is not None:
    18             self['rate'] = self.distance / self.time
    19 
    20 >>> rtd= RateTimeDistance( rate=6.3, time=8.25, distance=None )
    21 >>> print( "Rate={rate}, Time={time}, Distance={distance}".format(**rtd ) )
    22 Rate=6.3, Time=8.25, Distance=51.975
    23 # It's also important to note that once all three values are set, this object can't be changed to provide new solutions easily.
    24 # 上面有个bug在于,一旦我们想改变时间,这时发现速度与距离至少其一一定会变,按代码顺序是改变了距离,而如果我们不想改变距离而是改变速度就不行了
    25 # 或者是两个都不想改变,唯一的办法不改变其中一个就是先把一个值设为None
    26 
    27 # 解决办法:design a model that tracked the order that the variables were set in
    28 # this model could save us from having to clear one variable before setting another to recompute a related result.
    综合__settattr__,__getattr__,__dir__以及主动计算

       3:The __getattribute__() method

        总的来说,几乎没必要用__getattribute__(),其默认的方法已近够强大了,况且几乎所有我们需要的都能够通过__getattr__()实现。

     1 class BlackJackCard3:
     2     """Abstract Superclass"""
     3     def __init__( self, rank, suit, hard, soft ):
     4         super().__setattr__( 'rank', rank )
     5         super().__setattr__( 'suit', suit )
     6         super().__setattr__( 'hard', hard )
     7         super().__setattr__( 'soft', soft )
     8     def __setattr__( self, name, value ):
     9         if name in self.__dict__:
    10             raise AttributeError( "Cannot set {name}".format(name=name) )
    11         raise AttributeError( "'{__class__.__name__}' has no attribute'{name}'".format( __class__= self.__class__, name= name ) )
    12     def __getattribute__( self, name ):
    13         if name.startswith('_'):
    14             raise AttributeError
    15         return object.__getattribute__( self, name )
    16 
    17 >>> c = BlackJackCard3( 'A', '?', 1, 11 )
    18 >>> c.rank= 12
    19 Traceback (most recent call last):
    20 File "<stdin>", line 1, in <module>
    21 File "<stdin>", line 9, in __setattr__
    22 File "<stdin>", line 13, in __getattribute__
    23 AttributeError
    24 >>> c.__dict__['rank']= 12
    25 Traceback (most recent call last):
    26 File "<stdin>", line 1, in <module>
    27 File "<stdin>", line 13, in __getattribute__
    28 AttributeError
    __getattribute__

     四:descriptors

        Descriptor.__get__( self, instance, owner ),Descriptor.__set__( self, instance, value ),Descriptor.__delete__( self, instance )
    instance: the self variable of the object being accessed
    owner : the owning class object
    value : the new value that the descriptor needs to be set to.

    描述符是一个类:在达到属性前处理,可用于get,set,delete
    其本身在类定义时创建,并不是在__init__中创建,它是类的一部分,不同于方法以及属性
    用其来实现(不)可变对象:
    无数据描述符:实现__set__or__delete__ or both,若是immutable对象,只用实现__set__并返回AttributeError
    数据描述符: 至少实现__get__,通常实现__get__与__set__来创建个可变对象。

      1:无数据描述符

     1 class UnitValue_1:
     2     """Measure and Unit combined."""
     3     def __init__( self, unit ):
     4         self.value= None
     5         self.unit= unit
     6         self.default_format= "5.2f"
     7     def __set__( self, instance, value ):
     8         self.value= value
     9     def __str__( self ):
    10         return "{value:{spec}} {unit}".format( spec=self.default_format, **self.__dict__)
    11     def __format__( self, spec="5.2f" ):
    12         #print( "formatting", spec )
    13         if spec == "": spec= self.default_format
    14             return "{value:{spec}} {unit}".format( spec=spec,**self.__dict__)
    15 
    16 # The following is a class that does rate-time-distance calculations eagerly:
    17 class RTD_1:
    18     rate= UnitValue_1( "kt" )
    19     time= UnitValue_1( "hr" )
    20     distance= UnitValue_1( "nm" )
    21     def __init__( self, rate=None, time=None, distance=None ):
    22         if rate is None:
    23             self.time = time
    24             self.distance = distance
    25             self.rate = distance / time
    26         if time is None:
    27             self.rate = rate
    28             self.distance = distance
    29             self.time = distance / rate
    30         if distance is None:
    31             self.rate = rate
    32             self.time = time
    33             self.distance = rate * time
    34     def __str__( self ):
    35         return "rate: {0.rate} time: {0.time} distance:{0.distance}".format(self)
    36 
    37 # As soon as the object is created and the attributes loaded, the missing value is computed.
    38 # Once computed, the descriptor can be examined to get the value or the unit's name.
    39 # Additionally, the descriptor has a handy response to str() and formatting requests
    40 
    41 >>> m1 = RTD_1( rate=5.8, distance=12 )
    42 >>> str(m1)
    43 'rate: 5.80 kt time: 2.07 hr distance: 12.00 nm'
    44 >>> print( "Time:", m1.time.value, m1.time.unit )
    45 Time: 2.0689655172413794 hr
    无数据描述符的例子

      2:数据描述符,转换单位后自动更新

     1 class Unit:
     2     conversion= 1.0
     3     def __get__( self, instance, owner ):
     4         return instance.kph * self.conversion       #kph:千米每小时
     5     def __set__( self, instance, value ):
     6         instance.kph= value / self.conversion
     7 
     8 # The following are the two conversion descriptors:
     9 class Knots( Unit ):
    10     conversion= 0.5399568
    11 class MPH( Unit ):
    12     conversion= 0.62137119
    13 # The following is a unit descriptor for a standard unit, kilometers per hour:
    14 class KPH( Unit ):
    15     def __get__( self, instance, owner ):
    16         return instance._kph
    17     def __set__( self, instance, value ):
    18         instance._kph= value
    19 
    20 
    21 class Measurement:
    22     kph= KPH()
    23     knots= Knots()
    24     mph= MPH()
    25     def __init__( self, kph=None, mph=None, knots=None ):
    26         if kph:
    27             self.kph= kph
    28         elif mph:
    29             self.mph= mph
    30         elif knots:
    31             self.knots= knots
    32         else:
    33             raise TypeError
    34     def __str__( self ):
    35         return "rate: {0.kph} kph = {0.mph} mph = {0.knots}knots".format(self)
    36 
    37 # 在不同进制下自动完成转换
    38 >>> m2 = Measurement( knots=5.9 )
    39 >>> str(m2)
    40 'rate: 10.92680006993152 kph = 6.789598762345432 mph = 5.9 knots'
    41 >>> m2.kph
    42 10.92680006993152
    43 >>> m2.mph
    44 6.789598762345432
    数据描述符例子

    五:一些补充:

      Internally, Python uses descriptors to implement features such as method functions,
    static method functions, and properties. Many of the cool use cases for descriptors
    are already first-class features of the language

      
    In Python, it's considerably simpler to treat all attributes as public. This means the following:
    They should be well documented.
    They should properly reflect the state of the object; they shouldn't be temporary or transient values.
    In the rare case of an attribute that has a potentially confusing (or brittle)
      value, a single leading underscore character (_) marks the name as "not part
      of the defined interface." It's not really private.

      一般来说,外部能够改变属性值并不是严重的事,但是当一个属性值改变后会影响到另一个时,我们需要考虑用函数或者property进行一些设置。
    注意区别property的两种设计方式(eager calcilation & lazy calculation)


    descriptor是非常高级的python用法,一般用于连接 python 与 non-python 的处理,比如python与SQL,python做网络服务器,
    在我们的程序里,关于attributes我们尽量用property来实现,如果发现property需要写的太复杂,那么我们转向descriptor。
  • 相关阅读:
    mysql查询字段时实现左右补零
    两种方法获取MyBatis刚刚插入的id
    SpringBoot扫描包提示找不到mapper的问题
    如何删除mac keeper
    mongodb如何设置主键自增
    java数组和字符串相互转换
    algid parse error, not a sequence错误
    java数字转换成文字方法
    maven的groupid和artifactId
    mysql中的日期转换函数(类似oracle中的to_date)
  • 原文地址:https://www.cnblogs.com/pengsixiong/p/5382421.html
Copyright © 2020-2023  润新知