Python學習筆記(七)對象和類

什么是對象

Python中所有的數據都是以對象的形式存在,無論是簡單的數字類型還是復雜的代碼模塊。在Python中,當我們想要創建屬于自己的對象或者修改已有對象的行為時,才需要關注對象的內部實現細節。
對象既包含數據(變量,更習慣稱之為特性,attribute),也包含代碼(函數,也稱為方法)。它是某一類具體事物的特殊實例。。例如,整數 7 就是一個包含了加法、乘法之類方法的對 象,這些方法在 2.2 節曾經介紹過。整數 8 則是另一個對象。這意味著在 Python 里,7 和 8都屬于一個公共的類,我們稱之為整數類。

當要創建一個沒有創建過的新對象時,必須首先定義一個類,用以指明該類型的對象包含的內容(特性和方法)。
可以把對象想象成名詞,那么方法就是動詞。對象代表這一個獨立的事物,它的方法則定義了它時如何與其他事物相互作用的。
與模塊不同,我們可以同時創建很多同類的對象,它們的特性值可能各不相同。對象就像是包含了代碼的超級數據結構。


實用class定義類

類(class)就是制作對象的模具。例如,Python 的內置類 String 可以創建像 'cat' 和 'duck' 這樣的字符串對象。在python中想要創建對象,要首先通過class關鍵字創建一個類。

class Person():
    def __init__(self,name):
        self.name = name
hunter = Person('Elmer Fudd')

name參數作為對象的特性存儲在了對象里,可以直接進行讀寫

print('The nighty hunter:',hunter.name)
The nighty hunter: Elmer Fudd

其中的__init__()是python中特殊的對象初始化方法,用于根據類的定義創建實例對象。self參數指向了這個正在被創建的對象本身。當在類聲明里定義__init__()方法時,第一個參數必須為self。name為對象的一個屬性。

上面的代碼做了以下工作

創建Person類的定義
在內存中實例化(創建)一個新的對象
調用對象的__init__方法,將新創建的對象作為self傳入,并將另一個參數('Elmer Fudd')作為那么傳
將name的值存入對象
返回這個新的對象
將名字hunter與這個對象關聯

這個新對象與任何其他的 Python 對象一樣。你可以把它當作列表、元組、字典或集合中的 元素,也可以把它當作參數傳遞給函數,或者把它做為函數的返回結果

在類的定義中, __init__并不是必須的,只有當需要區分由該類創建的不同對象時,才需要指定__init__方法。


繼承

繼承能夠實現代碼的有效復用。我們習慣的將原始的類稱為父類,超類或者基類,將新的類稱作孩子類,子類或衍生類。
通過一個栗子了解繼承:

class Car():
    def exclaim(self):
        print("I'm a Car!")
class Yugo(Car):  
    pass
give_me_a_car = Car()
give_me_a_yugo = Yugo()
give_me_a_car.exclaim()
give_me_a_yugo.exclaim()

I'm a Car!
I'm a Car!

覆蓋方法

對父類的方法進行覆蓋重新后重新生成對象,調用方法

class Car():
    def exclaim(self):
        print("I'm a Car!")
class Yugo(Car):
    def exclaim(self):
        print("I'm a Yugo! Much like a Car, but more Yugo-ish.")
give_me_a_car = Car()
give_me_a_yugo = Yugo()
give_me_a_car.exclaim()
give_me_a_yugo.exclaim()

I'm a Car!
I'm a Yugo! Much like a Car, but more Yugo-ish.

新創建的子類自動繼承了父類的所有信息,通過重寫父類的方法,能夠實現方法的覆蓋。
在子類中能夠覆蓋任何父類的方法,包括__init__()。下面是修改__init__()
我們創建以Person類,在創建兩個子類MDPerson和JDPerson:

class Person():
    def __init__(self,name):
        self.name = name
class MDPerson(Person):
    def __init__(self,name):
        self.name = name
class JDPerson(Person):
    def __init__(self,name):
        self.name = name
person = Person('Fudd')
doctor = MDPerson('Fudd')
lawyer = JDPerson('Fudd')
print(person.name)#結果 Fudd
print(doctor.name)#結果Doctor:Fudd
print(lawyer.name)#結果Fudd:Esquire

在上面的栗子中,子類的初始方法__init__()接受的參數和父類Person一樣,但是存儲到對象內部name特性的值卻不同。


添加新方法

子類可以添加父類沒有的方法,當父類調用子類的方法的時候會報錯。

class Car():
    def exclaim(self):
        print("I'm a car")
class Yugo(Car):
    def exclaim(self):
        print("I'm a Yugo! Much like a Car, but more Yugo-ish")    
    def need_a_push(self):
        print("A little help here?")
give_me_a_car = Car()
give_me_a__yugo = Yugo()
give_me_a__yugo.need_a_push()#結果   A little help here?give_me_a_car.need_a_push()#結果 
Traceback (most recent call last):  File "G:/pyCharm/weather.py", line 12, in <module>    give_me_a_car.need_a_push()AttributeError: 'Car' object has no attribute 'need_a_push'

使用super從父類得到幫助

上面我們完成了在子類中重新覆蓋父類的方法,除此之外我們還可以使用super()來調用父類的方法。
下面的小栗子將會定義一個新的類EmailPerson,用于表示有電子郵箱的Person。

class Person():
    def __init__(self,name):
        self.name = nameclass 
EmailPerson(Person):
    def __init__(self,name,email):
        super().__init__(name)
        self.email = email
bob = EmailPerson('BObFraples','bob@frapples.com')
print(bob.name)
BOb Fraples

print(bob.email)
bob@frapples.com

在子類中定義__init__()方法時候,父類的__init__()方法會被覆蓋。因此,在子類中,父類的初始化方法并不會被自動調用,我們必須顯式調用它。以上代碼做了:

  • 通過super()方法獲取了父類Person的定義
  • 子類的__init__()調用了Person.__init__()方法。它會自動的將self參數傳遞給父類。因此,我們只需輸入其余的參數即可。在上面的栗子中,Person()能夠接受的其余參數是name。
  • self.email = email 使子類和父類有了區別
  • 創建EmailPerson類對象,然后訪問name和email屬性

為什么不像下面這樣定義 EmailPerson 類呢?

 class EmailPerson(Person): 
     def __init__(self, name, email): 
         self.name = name 
         self.email = email

確實可以這么做,但這有悖我們使用繼承的初衷。我們應該使用 super() 來讓 Person 完成 它應該做的事情,就像任何一個單純的 Person 對象一樣。除此之外,不這么寫還有另一個 好處:如果 Person 類的定義在未來發生改變,使用 super() 可以保證這些改變會自動反映 到 EmailPerson 類上,而不需要手動修改。


self的自辯

Python中必須把self設置為實例方法的第一個參數。Python使用self參數來找到正確的對象所包含的特性和方法。
還記得前面例子中的 Car 類嗎?再次調用 exclaim() 方法:

 car = Car() 
 car.exclaim() 
 I'm a Car!

Python 在背后做了以下兩件事情:

  • 查找car對象所屬的類(Car)
  • 把car對象作為self參數傳給Car類所包含的exclaim()方法

使用屬性對特性進行訪問和設置

Python里所有特性都是公開的,不過我們可以設置屬性對特性進行設置和訪問。

注意:我們在這里將property譯作屬性,將attribute譯作特性#

在下面的栗子中,我們定義一個duck類,僅僅包含一個hidden_name特性。我們不希望別人直接訪問這兩個特性,因此定義兩個方法:getter方法和setter方法,并將這兩個方法設置為name屬性。

class Duck():    
    def  __init__(self,input_name): 
        self.hidden_name = input_name
    def get_name(self):
        print('insert the getter')
        return  self.hidden_name
    def set_name(self,input_name):
        print('insert the setter')
        self.hidden_name  = input_name
    name = property(get_name,set_name)
fowl = Duck('Howard')
#當我們嘗試訪問對象的name特性時候,get_name()會被自動調用
print(fowl.name)
#也可以顯式的調用get_name()
print(fowl.get_name())
#當對name屬性進行賦值的時候,set_name()會被自動調用
fowl.name = 'Daffy'print(fowl.name)
#也可以顯式的調用set_name()
fowl.set_name = 'Daffy'
print(fowl.name)

另一種定義屬性的方法是使用修飾符(decorator)。下面的栗子會定義兩個不同的方法,它們的的名字都叫name(),但包含不同的修飾符:

  • @property,用于指示getter方法
  • @name.setter,用于指示setter方法
    代碼:
class Duck():
    def __init__ (self,input_name):
        self.hidden_name = input_name
    @property
    def name(self):
        print('inside the getter')
        return self.hidden_name
#函數名相同,但是參數不同
    @name.setter
    def name(self,input_name):
        print('inside the setter')
        self.hidden_name = input_name
fowl = Duck('Howard')
#我們仍然可以像之前一樣訪問特性,只是沒有了get_name()和set_name()方法。
print(fowl.name)
fowl.name = 'Donald'
print(fowl.name)

注意:#這里我們仍然可以通過fowl.hidden_name 進行讀寫操作

之前的栗子我們使用name屬性指向類中存儲的某一特性,除此之外是,屬性還可以指向一個計算結果值。我們定義一個circle類,它包含radius特性以及一個計算屬性diameter:

class Circle():
    def __init__(self,radius):
        self.radius = radius
    @property
    def diameter(self):
        return  2 * self.radius
c = Circle(5)
print(c.radius)
#可以像訪問特性(例如radius)一樣訪問屬性diameter:
print(c.diameter)
#如果我們沒有指定某一特性的setter屬性(@diameter.setter),那么無法從類的外部對它的值進行設置。對于只讀的特性非常有用。
c.diameter = 20
Traceback (most recent call last):
 File "G:/pyCharm/weather.py", line 10, in <module>
    c.diameter = 20
AttributeError: can't set attribute

使用名稱重整保護私有特性

Python對那些需要刻意隱藏在類內部的特性有自己的命名規范:由連續的兩個下劃線開頭(__)。
我們把之前的hidden_name改名為__name:

class Duck():
    def __init__ (self,input_name):
        self.__name = input_name
    @property
    def name(self):
        print('inside the getter')
        return self.__name
#函數名相同,但是參數不同
    @name.setter
    def name(self,input_name):
        print('inside the setter')
        self.__name = input_name
#代碼能夠正常工作
fowl = Duck('Howard')
fowl.name
fowl.name = 'Donald'
fowl.name
#但是此時我們無法在外部訪問__name特性了:
print(fowl.__name)
Traceback (most recent call last):
  File "G:/pyCharm/sources/daily.py", line 13, in <module>
    print(fowl.__name)
AttributeError: 'Duck' object has no attribute '__name'

這種命名規范本質上并沒有把特性變成私有,但 Python 確實將它的名字重整了,讓外部 的代碼無法使用。名稱重整是怎么實現的:
fowl._Duck__name
'Donald'
我們并沒有得到 inside the getter,成功繞過了 getter 方法。盡管如我們所 見,這種保護特性的方式并不完美,但它確實能在一定程度上避免我們無意或有意地對特 性進行直接訪問。


方法的類型

有些數據(特性)和函數(方法)是類的一部分,還有一些是由類創建的實例的一部分。
在類的定義中,以 self 作為第一個參數的方法都是實例方法(instance method)。它們在 創建自定義類時最常用。實例方法的首個參數是 self,當它被調用時,Python 會把調用該 方法的對象作為 self 參數傳入。
與之相對,類方法(class method)會作用于整個類,對類作出的任何改變會對它的所有實 例對象產生影響。在類定義內部,用前綴修飾符 @classmethod 指定的方法都是類方法。與 實例方法類似,類方法的第一個參數是類本身。在 Python 中,這個參數常被寫作 cls,因 為全稱 class 是保留字,在這里我們無法使用。下面的例子中,我們為類 A 定義一個類方 法來記錄一共有多少個類 A 的對象被創建:

class A():
    count = 0
    def __init__(self):
        A.count += 1
    def exclaim(self):
        print("I'm an A")
    @classmethod
    def kids(cls):
        print("A has",cls.count,"little objects.")
easy_a = A()
breezy_a = A()
wheezy_a = A()
A.kids()#A has 3 little objects.

上面的代碼中,我們使用的是 A.count(類特性),而不是 self.count(可能是對象 的特性)。在 kids() 方法中,我們使用的是 cls.count,它與 A.count 的作用一樣。
類定義中的方法還存在著第三種類型,它既不會影響類也不會影響類的對象。它們出現在 類的定義中僅僅是為了方便,否則它們只能孤零零地出現在代碼的其他地方,這會影響代 碼的邏輯性。這種類型的方法被稱作靜態方法(static method),用@staticmethod修飾,它既不需要self參數也不需要class參數。

class Coyote():
    @staticmethod
    def commercial():
        print('This CoyoteWeapon has been brought to you by Acme')
#我們甚至不用創建任何Coyote類的對象就可以調用這個方法
Coyote.commercial()

鴨子類型

Python對實現多態(polymorphsim)要求十分寬松,意味著我們可以對不同的對象調用同名的操作,甚至不用管這些對象的類型是什么。
我們來為三個 Quote 類設定同樣的初始化方法 init(),然后再添加兩個新函數:

  • who()返回保存的person字符串的值
  • says()返回保存的words字符串的內容,并填上指定的標點符號。
    代碼如下:
class Quote():
    def __init__(self,person,words):
        self.person = person
        self.words = words
    def who(self):
        return  self.person
    def says(self):
        return  self.words + '.'
class QuestionQuote(Quote):
    def says(self):
        return  self.words + '?'
class ExclamationQuote(Quote):
    def says(self):
        return self.words + '!'
#接下來創建一些對象
hunter = Quote('Alili',"I'm hunting wabbits")
print(hunter.who(),"says:",hunter.says())
#Alili says: I'm hunting wabbits.
hunted1 = QuestionQuote('Bugs Bunny', "What's up, doc")
print(hunted1.who(), 'says:', hunted1.says())
#Bugs Bunny says: What's up, doc?
hunted2 = ExclamationQuote('Daffy Duck', "It's rabbit season")
print(hunted2.who(),"says:",hunted2.says())
#Daffy Duck says: It's rabbit season!

我們不需要改變 QuestionQuote 或者 ExclamationQuote 的初始化方式,因此沒有覆蓋它們 的 __init__()方法。Python 會自動調用父類 Quote 的初始化函數 __init__() 來存儲實例 變量 person 和 words,這就是我們可以在子類 QuestionQuote 和 ExclamationQuote 的對象 里訪問 self.words 的原因
三個不同版本的 says() 為上面三種類提供了不同的響應方式,這是面向對象的語言中多態 的傳統形式。Python 在這方面走得更遠一些,無論對象的種類是什么,只要包含 who() 和 says(),你便可以調用它。##我們再來定義一個 BabblingBrook 類,它與我們之前的獵人獵 物(Quote 類的后代)什么的沒有任何關系

class BabblingBrook():
    def who(self):
        return 'Brook'
    def says(self):
        return  'Baabble'
brook = BabblingBrook()
#現在對不同對象執行who()和says()方法,其中有一個(brook)與其他類型額對象毫無關聯:
def who_says(obj):
    print(obj.who(),"syas:",obj.says())
who_says(hunter)
#Alili syas: I'm hunting wabbits.
who_says(hunted1)
#Bugs Bunny syas: What's up, doc?
who_says(hunted2)
#Daffy Duck syas: It's rabbit season!
who_says(brook)
#Brook syas: Baabble

特殊方法

Python中的特殊方法的名稱以雙下劃線(__)開頭和結束。沒錯,我們已經見過其中一個:__ init__,它根據類的定義以及傳入的參數對新創建的對象進行初始化。

假設你有一個簡單的 Word 類,現在想要添加一個 equals() 方法來比較兩個詞是否一致, 忽略大小寫。也就是說,一個包含值 'ha' 的 Word 對象與包含 'HA' 的是相同的。
下面的代碼是第一次嘗試,創建一個普通方法 equals()。self.text 是當前 Word 對象所包 含的字符串文本,equals() 方法將該字符串與 word2(另一個 Word 對象)所包含的字符串 做比較:

class Word():
    def __init__(self,text):
        self.text = text
    def equals(self,word2):
        return  self.text.lower() == word2.text.lower()
#創建三個包含不同字符串的對象
first = Word('ha')
second = Word('HA')
third = Word('eh')
#進行比較
print(first.equals(second))#True
print(first.equals(third))#False

我們成功定義了 equals() 方法來進行小寫轉換并比較。但試想一下,如果能通過 if first == second 進行比較的話豈不更妙?這樣類會更自然,表現得更像一個 Python 內置的類。 把前面例子中的 equals() 方法的名稱改為 __eq__()

class Word():
    def __init__(self,text):
        self.text = text
    def __eq__(self,word2):
        return  self.text.lower() == word2.text.lower()
#創建三個包含不同字符串的對象
first = Word('ha')
second = Word('HA')
third = Word('eh')
#進行比較
print(first == second)#True
print(first == third)#False

這里我們僅僅將方法名修改為 eq()很神奇的就出現了。
和比較相關的魔術方法

方法名                     使用 
`__eq__(self, other)`    self == other 
`__ne__(self, other)`    self != other
`__lt__(self, other)`      self < other
`__gt__(self, other)`     self > other
`__le__(self, other)`     self <= other
`__ge__(self, other)`    self >= other

和數學相關的黑魔法

方法名                           使用 
`__add__(self, other)`        self + other
`__sub__(self, other)`        self - other
`__mul__(self, other) `       self * other
`__?oordiv__(self, other)`  self // other
`__truediv__(self, other)`   self / other
`__mod__(self, other)`       self % other
`__pow__(self, other)`       self ** other

不僅數字類型可以使用像 +(魔術方法 __add__())和 -(魔術方法 __sub__())的數學運算 符,一些其他的類型也可以使用。例如,Python 的字符串類型使用 + 進行拼接,使用 * 進 行復制
其他種類的魔術方法

方法名 使用 
`__str__(self)` str(self)
`__repr__(self)` repr(self)
`__len__(self)` len(self)

除了 __init__() 外,你會發現在編寫類方法時最常用到的是__str__(),它用于定義如何 打印對象信息。print() 方法,str() 方法都會用到__str__()。交互式解釋器則用 __repr__() 方法輸出變量。如果在你的類 既沒有定義__str__()也沒有定義 __repr__(),Python 會輸出類似下面這樣的默認字符串:

first = Word('ha')
first 
<__main__.Word object at 0x1006ba3d0>
print(first) 
<__main__.Word object at 0x1006ba3d0>
我們將 `__str__()` 和 `__repr__() `方法都添加到 Word 類里,讓輸出的對象信息變得更好看些:
class Word():
    def __init__(self, text):
    self.text = text 
    def __eq__(self, word2):
    return self.text.lower() == word2.text.lower()
    def __str__(self): 
    return self.text 
    def __repr__(self): 
    return 'Word("'  self.text  '")'
first = Word('ha') 
first        # uses __repr__ 
Word("ha") 
print(first) # uses __str__ 
ha

組合

如果你想要創建的子類在大多數情況下的行為都和父類相似的話(子類是父類的一種特 殊情況,它們之間是 is-a 的關系),使用繼承是非常不錯的選擇。建立復雜的繼承關系確 實很吸引人,但有些時候使用組合(composition)或聚合(aggregation)更加符合現實的 邏輯(x 含有 y,它們之間是 has-a 的關系)。一只鴨子是鳥的一種(is-a),它有一條尾巴 (has-a)。尾巴并不是鴨子的一種,它是鴨子的組成部分。下個例子中,我們會建立 bill 和 tail 對象,并將它們都提供給 duck 使用:

class Bill():
    def __init__(self,description):
        self.description = description
class Tail():
    def __init__(self,length):
        self.length = length
class Duck():
    def __init__(self,bill,tail):
        self.bill = bill
        self.tail = tail
    def about(self):
        print('This duck has a',bill.description,'bill and a',tail.length,'tail')
tail = Tail('long')
bill = Bill('wide orage')
duck = Duck(bill,tail)
duck.about()
#結果This duck has a wide orage bill and a long tail

注:本文內容來自《Python語言及其應用》歡迎購買原書閱讀

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容