python面對對象編程-------5:獲取屬性的四種辦法:@property, __setattr__(__getattr__) ,descriptor

一:最基本的屬性操做python

 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'
基本的屬性操做

 

二:@propertyweb

  被@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片斷的代碼app

 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

 

三:屬性獲取的特殊方法ide

  __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。
相關文章
相關標籤/搜索