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
三:属性获取的特殊方法
__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机制
版本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
# 注意上面两个版本是有区别的,在版本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.
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
四: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。