python面對對象編程------4:類基本的特殊方法__str__,__repr__,__hash__,__new__,__bool__,6大比較方法


一:string相關:__str__(),__repr__(),__format__()
  str方法更面向人類閱讀,print()使用的就是str
  repr方法更面對python,目標是希望生成一個放入eval方法就能夠執行的python語句字符串
  注意,不要一看到format方法就認為他是用來取代%賦值的
*在里format方法可通過后面的!r與!s來指定使用repr還是str,即此時就不是用的format方法了,而是調用的repr或者str
   format有兩種參數形式:
   1:"",此類可以直接用str(...)來返回
   2:format(someobject, specification),
   e.g:"{0:06.4f}", the 06.4f is the format specification that applies to item 0 of the argument list to be formatted
   format很少用到,在此先略過(書61頁)
 1 class Hand:
 2     def __init__( self, name, *friends ):
 3         self.name = name
 4         self.friends= list(friends)
 5 
 6     def __str__( self ):
 7         return ", ".join( map(str, self.friends) )
 8 
 9     def __repr__( self ):
10         return "{__class__.__name__}({self.name!r}, {_cards_str})".format(__class__=self.__class__,_cards_str=", ".join( map(repr, self.friends) ),self=self)
11 
12 a = Hand("pd","DK","Nancy")
13 print(str(a))
14 print(repr(a))
15 # 輸出:
16 # DK, Nancy, yao
17 # Hand('pd', 'DK', 'Nancy')
__str__&__repr__

 

二:__hash__(),__eq__()

  python有兩個hash庫,密碼學的hashlib,zlib的adler32()與crc32()
  對於簡單數字這兩個庫都不用,直接用hash函數就行,hash函數常用於set,dict等定位其中元素
  python每個對象都有個id,本質上id是其內存地址,is比較是基於id的,可用id(x)查看其值,而基類object的__hash__方法就是將其id/16取整后作為integer返回:
  需要注意的是只有immutable數值類型才能用hash方法,list與dict沒有。所以,如果我們創建的是mutable的對象,就讓hash函數返回None就行了

  __eq__() 方法,用於==對比,是基於hash值的。

  對於immutable object,hash返回基於id的變數,eq用id相比就可以了。而mutable object寫eq,hash返回None
 1 #使用默認eq與hash
 2 class Card:
 3     insure= False
 4     def __init__( self, rank, suit, hard, soft ):
 5         self.rank= rank
 6         self.suit= suit
 7         self.hard= hard
 8         self.soft= soft
 9     def __repr__( self ):
10         return "{__class__.__name__}(suit={suit!r}, rank={rank!r})".format(__class__=self.__class__, **self.__dict__)
11     def __str__( self ):
12         return "{rank}{suit}".format(**self.__dict__)
13 class NumberCard( Card ):
14     def __init__( self, rank, suit ):
15         super().__init__( str(rank), suit, rank, rank )
16 class AceCard( Card ):
17     def __init__( self, rank, suit ):
18         super().__init__( "A", suit, 1, 11 )
19 class FaceCard( Card ):
20     def __init__( self, rank, suit ):
21         super().__init__( {11: 'J', 12: 'Q', 13: 'K' }[rank], suit, 10, 10 )
22 
23 c1 = AceCard( 1, '?' )
24 c2 = AceCard( 1, '?' )
25 
26 print(id(c1),id(c2))
27 print(id(c1)/16,id(c2)/16)
28 print(hash(c1),hash(c2))
29 print(c1==c2)
30 print(c1 is c2)
31 print( set([c1,c2]) )
32 
33 # 輸出:
34 # 42444656 42444688
35 # 2652791.0 2652793.0
36 # 2652791 2652793
37 # False
38 # False
39 # {AceCard(suit='?', rank='A'), AceCard(suit='?', rank='A')}
使用默認的__hash__與__eq__
  由上可以看出:
同一個類參數相同的兩個實例其各種比較[id, is, ==, hash,set去重]都不一樣
在現實中使用是有問題的,比如這里面如果我們要比較兩張牌則這兩張應該看為相同,所以我們需要改寫其eq方法,讓其不基於id來達到目的
 1 #改進版,重寫eq與hash
 2 class Card2:
 3     insure= False
 4     def __init__( self, rank, suit, hard, soft ):
 5         self.rank= rank
 6         self.suit= suit
 7         self.hard= hard
 8         self.soft= soft
 9     def __repr__( self ):
10         return "{__class__.__name__}(suit={suit!r}, rank={rank!r})".format(__class__=self.__class__, **self.__dict__)
11     def __str__( self ):
12         return "{rank}{suit}".format(**self.__dict__)
13 
14     def __eq__( self, other ):
15         return self.suit == other.suit and self.rank == other.rank
16 
17     def __hash__( self ):
18         return hash(self.suit) ^ hash(self.rank)
19 
20 class AceCard2( Card2 ):
21     insure= True
22     def __init__( self, rank, suit ):
23         super().__init__( "A", suit, 1, 11 )
24 
25 c1 = AceCard2( 1, '?' )
26 c2 = AceCard2( 1, '?' )
27 
28 print(id(c1),id(c2))
29 print(id(c1)/16,id(c2)/16)
30 print(hash(c1),hash(c2))            #變為相等的數字,但是需要注意的是已經不是 id/16
31 print(c1==c2)                       #變為True
32 print(c1 is c2)
33 print( set([c1,c2]) )
重寫__hash__與__eq__

 

   對於mutable object,在這里依然用card做示范,但其實是不貼切的,card應該是immutable的。注意hash返回None的寫法

 1 class Card3:
 2     insure= False
 3     def __init__( self, rank, suit, hard, soft ):
 4         self.rank= rank
 5         self.suit= suit
 6         self.hard= hard
 7         self.soft= soft
 8     def __repr__( self ):
 9         return "{__class__.__name__}(suit={suit!r}, rank={rank!r})".format(__class__=self.__class__, **self.__dict__)
10     def __str__( self ):
11         return "{rank}{suit}".format(**self.__dict__)
12 
13     def __eq__( self, other ):
14         return self.suit == other.suit and self.rank == other.rank
15         # and self.hard == other.hard and self.soft == other.soft
16 
17     __hash__ = None         #!!!!!!!!!
18 
19 class AceCard3( Card3 ):
20     insure= True
21     def __init__( self, rank, suit ):
22         super().__init__( "A", suit, 1, 11 )
23 
24 
25 c1 = AceCard3( 1, '?' )
26 c2 = AceCard3( 1, '?' )
27 
28 print(id(c1),id(c2))
29 print(id(c1)/16,id(c2)/16)
30 print(hash(c1),hash(c2))              #報錯:TypeError: unhashable type: 'AceCard3'
31 print(c1==c2)                         #True
32 print(c1 is c2)
33 print( set([c1,c2]) )                 #報錯:TypeError: unhashable type: 'AceCard3',由於不能hash,自然不能用於set數據結構
mutable object的__hash__與_eq__

 

  對於mutable object,若想對其實例進行數值分析,
可以寫一個immutable的子類,將實例傳入后完全copy下來,再對copy份進行寫hash處理:
如下面的Hand類,可以寫一個不可變的FrozenHand類來對其進行hash數值處理
 1 class Hand:
 2     def __init__( self, dealer_card, *cards ):
 3         self.dealer_card= dealer_card
 4         self.cards= list(cards)
 5     def __str__( self ):
 6         return ", ".join( map(str, self.cards) )
 7     def __repr__( self ):
 8         return "{__class__.__name__}({dealer_card!r}, {_cards_str})".format(__class__=self.__class__,_cards_str=", ".join( map(repr, self.cards) ),**self.__dict__ )
 9     def __eq__( self, other ):
10         return self.cards == other.cards and self.dealer_card ==other.dealer_card
11     __hash__ = None
12 
13 import sys
14 class FrozenHand( Hand ):
15     def __init__( self, *args, **kw ):
16         if len(args) == 1 and isinstance(args[0], Hand):
17             # Clone a hand
18             other= args[0]
19             self.dealer_card= other.dealer_card
20             self.cards= other.cards
21         else:
22             # Build a fresh hand
23             super().__init__( *args, **kw )
24     def __hash__( self ):
25         h= 0
26         for c in self.cards:
27             h = (h + hash(c)) % sys.hash_info.modulus
28         return h
29 
30 stats = defaultdict(int)
31 d= Deck()                               #Deck是一堆牌
32 h = Hand( d.pop(), d.pop(), d.pop() )
33 h_f = FrozenHand( h )
34 stats[h_f] += 1
mutable object的hash處理

 

三:__bool__

  使用:
   if xxx:
   ... #True
   else:
   ... #False
  python認為為False的情況:
   1:False
   2: 0
   3:空:‘’,【】,(),{}
  注:自帶的bool函數可用於測定並返回True還是False,
   如 bool(0),bool([]),bool('')都返回False
1 #對於Deck類,添加__bool__方法:
2 def __bool__( self ):
3     return bool( self._cards )
4 
5 #如果是繼承 list 類,可能如下書寫:
6 def __bool__( self ):
7     return super().__bool__( self )
__bool__

 

四:6大比較方法

     x<y calls x.__lt__(y)
x<=y calls x.__le__(y)
x==y calls x.__eq__(y)
x!=y calls x.__ne__(y)
x>y calls x.__gt__(y)
x>=y calls x.__ge__(y)
 1 class BlackJackCard_p:
 2     def __init__( self, rank, suit ):
 3         self.rank= rank
 4         self.suit= suit
 5 
 6     def __lt__( self, other ):
 7         print( "Compare {0} < {1}".format( self, other ) )
 8         return self.rank < other.rank
 9 
10     def __str__( self ):
11         return "{rank}{suit}".format( **self.__dict__ )
12 
13 >>> two = BlackJackCard_p( 2, '?' )
14 >>> three = BlackJackCard_p( 3, '?' )
15 >>> two < three
16 Compare 2? < 3?       (*17 True
18 >>> two > three       (*19 Compare 3? < 2?
20 False
21 >>> two == three
22 False
23 >>> two <= three
24 Traceback (most recent call last):
25 File "<stdin>", line 1, in <module>
26 TypeError: unorderable types: BlackJackCard_p() <= BlackJackCard_p()
用撲克牌來測試__lt__
   由(*)看出:  two < three的比較時用的是two.__lt__(three)
   two>three由於沒有__gt__(),用的是three.__lt__(two)
   由於這種機制,我們可以只提供四種方法來包含上面六種:
         __eq__(), __ne__(), __lt__(), __le__().

  1:同類實例比較
 1 class BlackJackCard:
 2     def __init__( self, rank, suit, hard, soft ):
 3         self.rank= rank
 4         self.suit= suit
 5         self.hard= hard
 6         self.soft= soft
 7     def __lt__( self, other ):
 8         if not isinstance( other, BlackJackCard ):
 9             return NotImplemented
10         return self.rank < other.rank
11     def __le__( self, other ):
12         try:
13             return self.rank <= other.rank
14         except AttributeError:
15             return NotImplemented
16     def __gt__( self, other ):
17         if not isinstance( other, BlackJackCard ):
18             return NotImplemented
19         return self.rank > other.rank
20     def __ge__( self, other ):
21         if not isinstance( other, BlackJackCard ):
22             return NotImplemented
23         return self.rank >= other.rank
24     def __eq__( self, other ):
25         if not isinstance( other, BlackJackCard ):
26             return NotImplemented
27         return self.rank == other.rank and self.suit == other.suit      #比較==時多了對於suit的檢查,而比較大小時只比較了rank
28     def __ne__( self, other ):
29         if not isinstance( other, BlackJackCard ):
30             return NotImplemented
31 """
32 注意其上實現的六個比較方法中有兩種檢驗方式:
33     1:explicit的用isinstance檢驗是不是BlackJackCard的類實例
34     2:implicit的用try語句,此種除非是某個類剛好有rank屬性才會發生比較,
35     實際上第二種方法更好,因錯出現剛好有rank屬性的類又用來比較的概率十分小,而可以用來擴展為別的紙牌游戲的牌與之的比較
36 """
37 >>> two = card21( 2, '?' )
38 >>> three = card21( 3, '?' )
39 >>> two_c = card21( 2, '?' )
40 
41 >>> two == two_c
42 False
43 >>> two.rank == two_c.rank
44 True
45 >>> two < three
46 True
47 >>> two_c < three
48 True
49 >>> two < 2                                 #報錯是因為__lt__()方法用isinstance檢驗了類型,非同類就報錯
50 Traceback (most recent call last):
51 File "<stdin>", line 1, in <module>
52 TypeError: unorderable types: Number21Card() < int()
53 >>> two == 2                                #此地沒有報錯是因為遇到NotImplemented,python會交換他們.在此即是變成int.__eq__()
54 False
同類實例比較的實現

 

    2:異類實例比較

 1 #下面主要是用isinstance來判斷相應的可能的異類的類型,再做處理
 2 class Hand:
 3     def __init__( self, dealer_card, *cards ):
 4         self.dealer_card= dealer_card
 5         self.cards= list(cards)
 6     def __str__( self ):
 7         return ", ".join( map(str, self.cards) )
 8     def __repr__( self ):
 9         return "{__class__.__name__}({dealer_card!r}, {_cards_str})".format(__class__=self.__class__,_cards_str=", ".join( map(repr, self.cards) ),**self.__dict__ )
10     def __eq__( self, other ):
11         if isinstance(other,int):
12             return self.total() == other
13         try:
14             return (self.cards == other.cards and self.dealer_card == other.dealer_card)
15         except AttributeError:
16             return NotImplemented
17     def __lt__( self, other ):
18         if isinstance(other,int):
19             return self.total() < other
20         try:
21             return self.total() < other.total()
22         except AttributeError:
23             return NotImplemented
24     def __le__( self, other ):
25         if isinstance(other,int):
26             return self.total() <= other
27         try:
28             return self.total() <= other.total()
29         except AttributeError:
30             return NotImplemented
31     __hash__ = None
32     def total( self ):
33         delta_soft = max( c.soft-c.hard for c in self.cards )
34         hard = sum( c.hard for c in self.cards )
35         if hard+delta_soft <= 21:
36             return hard+delta_soft
37         return hard
38 
39 >>> two = card21( 2, '?' )
40 >>> three = card21( 3, '?' )
41 >>> two_c = card21( 2, '?' )
42 >>> ace = card21( 1, '?' )
43 >>> cards = [ ace, two, two_c, three ]
44 >>> h= Hand( card21(10,'?'), *cards )
45 >>> print(h)
46 A?, 2?, 2?, 3?
47 >>> h.total()
48 18
49 >>> h2= Hand( card21(10,'?'), card21(5,'?'), *cards )
50 >>> print(h2)
51 5?, A?, 2?, 2?, 3?
52 >>> h2.total()
53 13
54 >>> h < h2
55 False
56 >>> h > h2
57 True
58 >>> h == 18
59 True
60 >>> h < 19
61 True
62 >>> h > 17
63 Traceback (most recent call last):
64 File "<stdin>", line 1, in <module>
65 TypeError: unorderable types: Hand() > int()
異類實例的比較

 

五:immutable object的__new__

  首先說明為何講immutable object的__new__,因為mutable object可以直接在__init__中加入新參數而immutable object是不行的:

1 # 可變對象:
2 class cL(list):
3     def __init__(self,value,mutable):
4         super().__init__(value)
5         self.mutable = mutable
6 aha = cL([2,3,4],'mumu')
7 #注意此處的value參數,用help(list)可看到:list(iterable) -> new list initialized from iterable's items
8 print(aha,aha.mutable)
9 # 輸出:[2, 3, 4] mumu,可見多了個mutable屬性
繼承mutable object后添加屬性

  __new__()默認就是staticmethod,不需要寫@satticmethod
  對於不可變對象的子類並不能夠直接通過__init__方法創造更多的屬性,只有通過__new__方法才行
  __new__( cls, *args, **kw ).
   cls:即將要實例化的類,此參數在實例化時由Python解釋器自動提供
   *arg & **kw: with the exception of the cls argument, will be passed to __init__() as part of the standard Python behavior.
  __new__必須要有返回值,返回實例化出來的實例,且在后一步的__init__方法中就是對這個返回的實例進行操作
  默認的__new__()方法調用的是super().__new__(cls)
 1 class Float_Units( float ):
 2     def __new__( cls, value, unit ):            
 3         obj= super().__new__( cls, value )  #實例化父類object,並用value初始化
 4         obj.unit= unit                      #給父類object添加了屬性unit,這樣由於繼承關系也就有了此屬性
 5         return obj                          #返回實例
 6 speed= Float_Units( 6.5, "knots" )
 7 print(speed)
 8 print(speed.unit)
 9 # 輸出:
10 # 6.5
11 # knots     可見,能夠賦值value以及添加屬性unit
__new__為immutable object添加屬性

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM