什么是對象
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語言及其應用》歡迎購買原書閱讀