你需要了解的比較重要的Python概念
Python很容易學(xué)習(xí)。 但是,它具有某些難以理解的方面,例如類(lèi)和對(duì)象的世界。 在本文中,您將學(xué)習(xí):
- 在Python中,一切都是對(duì)象
- 如何創(chuàng)建自己的類(lèi)和對(duì)象
- 什么是繼承,以及如何利用它來(lái)發(fā)揮自己的優(yōu)勢(shì)
通過(guò)使Python對(duì)象神秘化,您對(duì)語(yǔ)言的理解將大大增加!
對(duì)象
對(duì)象在Python中起著核心作用。 讓我們來(lái)看看如何加深對(duì)主題的理解。
引擎蓋下
您可能知道內(nèi)置的len函數(shù)。 它返回您給它的對(duì)象的長(zhǎng)度。 但是,數(shù)字五的長(zhǎng)度是多少? 讓我們問(wèn)一下Python:
>>> len(5)
Traceback (most recent call last): File "
我喜歡錯(cuò)誤,因?yàn)樗鼈冋f(shuō)明了Python在內(nèi)部的工作方式。 在這種情況下,Python告訴我們5是一個(gè)對(duì)象,并且沒(méi)有l(wèi)en()。
在Python中,一切都是對(duì)象。 字符串,布爾值,數(shù)字甚至函數(shù)都是對(duì)象。
我們可以使用內(nèi)置函數(shù)dir()檢查REPL中的對(duì)象。 當(dāng)我們?cè)跀?shù)字5上嘗試dir時(shí),它將顯示出一個(gè)包含在任何數(shù)字對(duì)象中的函數(shù)的大列表:
>>> dir(5)
['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', ...'__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes']
為了清楚起見(jiàn),我將列表略去了一些。
該列表以這些帶有下劃線的怪異命名函數(shù)開(kāi)頭,例如__add__。 這些方法稱(chēng)為魔術(shù)方法或dunder(雙下劃線的縮寫(xiě))方法。
如果仔細(xì)觀察,您會(huì)發(fā)現(xiàn)int類(lèi)型的對(duì)象沒(méi)有__len__ dunder方法。 這就是Python的len()函數(shù)如何知道數(shù)字沒(méi)有長(zhǎng)度的原因。 len()所做的全部工作就是在提供它的對(duì)象上調(diào)用__len __()方法。 這也是為什么Python抱怨" int"類(lèi)型的對(duì)象沒(méi)有l(wèi)en()的原因。
我在這里隨便介紹了方法一詞。 讓我更正式地定義它:
當(dāng)函數(shù)是對(duì)象的一部分時(shí),我們稱(chēng)其為方法。
因此,如果字符串確實(shí)有長(zhǎng)度,那么它必須具有__len__方法,對(duì)嗎? 找出答案吧!
>>> dir("test")
['__add__', '__class__','__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']
是的,那里。 由于這是一種方法,因此我們也可以調(diào)用它:
>>> "test".__len__()
4
這等效于len(" test"),但不夠優(yōu)雅。 所以不要這樣做,只是為了說(shuō)明這些東西是如何工作的。
dir()還向我們展示了其他一些不太神奇的方法。 隨意嘗試一些,例如islower:
>>> "test".islower()
True
此方法檢查整個(gè)字符串是否為小寫(xiě),因此Python返回布爾值True。 其中一些方法需要一個(gè)或多個(gè)參數(shù),例如replace:
>>> 'abcd'.replace('a', 'b')
'bbcd'
它將所有出現(xiàn)的" a"替換為" b"。
什么是對(duì)象
現(xiàn)在我們已經(jīng)使用了對(duì)象,并且知道Python中的所有內(nèi)容都是對(duì)象,是時(shí)候定義什么是對(duì)象了:
對(duì)象是數(shù)據(jù)(變量)的集合以及對(duì)該數(shù)據(jù)進(jìn)行操作的方法
對(duì)象和面向?qū)ο蟮木幊淌窃?990年代初期流行的概念。 像C這樣的早期計(jì)算機(jī)語(yǔ)言沒(méi)有對(duì)象的概念。 但是,事實(shí)證明,對(duì)象是人類(lèi)易于理解的范例-可用于對(duì)許多現(xiàn)實(shí)情況進(jìn)行建模。
如今,大多數(shù)(如果不是全部)新語(yǔ)言都具有對(duì)象的概念。 因此,您將要學(xué)習(xí)的內(nèi)容在概念上也將適用于其他語(yǔ)言。
由于對(duì)象是Python語(yǔ)言的基礎(chǔ),因此您也可以自己創(chuàng)建對(duì)象,這是合乎邏輯的。 為此,我們需要首先定義一個(gè)類(lèi)。
類(lèi)
如果要?jiǎng)?chuàng)建自己的對(duì)象類(lèi)型,則首先需要定義它具有的方法和可以容納的數(shù)據(jù)。 該藍(lán)圖稱(chēng)為類(lèi)。
類(lèi)是對(duì)象的藍(lán)圖
所有對(duì)象都基于一個(gè)類(lèi)。 創(chuàng)建對(duì)象時(shí),我們將其稱(chēng)為"創(chuàng)建類(lèi)的實(shí)例"。 字符串,數(shù)字甚至布爾值也是類(lèi)的實(shí)例。 讓我們探索一下內(nèi)置函數(shù)類(lèi)型:
>>> type('a')
>>> type(1)
type(True)
顯然,有一些類(lèi)叫做str,int和bool。 這些是Python的一些本機(jī)類(lèi),但我們也可以構(gòu)建自己的類(lèi)!
如果沒(méi)有汽車(chē)的類(lèi)比,那么沒(méi)有一部教程是完整的,因此讓我們創(chuàng)建一個(gè)代表汽車(chē)的類(lèi)。 輸入的內(nèi)容很多,您必須重新開(kāi)始每個(gè)錯(cuò)誤。 隨時(shí)嘗試,但是如果您想走捷徑,我了解。 只需將以下內(nèi)容復(fù)制并粘貼到您的Python REPL中:
- class Car:
- speed = 0
- started = False
- def start(self):
- self.started = True
- print("Car started, let's ride!")
- def increase_speed(self, delta):
- if self.started:
- self.speed = self.speed + delta
- print('Vrooooom!')
- else:
- print("You need to start the car first")
- def stop(self):
- self.speed = 0
- print('Halting')
不用擔(dān)心,我們將逐步進(jìn)行介紹,但首先創(chuàng)建并使用Car類(lèi)型的對(duì)象:
>>> car = Car()
>>> car.increase_speed(10)
You need to start the car first
>>> car.start()
Car started, let's ride!
>>> car.increase_speed(40)
Vrooooom!
>>> _
對(duì)象始終是類(lèi)的實(shí)例。 一類(lèi)可以有許多實(shí)例。 我們只是使用Car()創(chuàng)建了Car類(lèi)的實(shí)例,并將其分配給可變car。 創(chuàng)建實(shí)例就像調(diào)用函數(shù)一樣-稍后將了解原因。
接下來(lái),我們?cè)谄?chē)對(duì)象上調(diào)用一種方法:嘗試在尚未啟動(dòng)時(shí)提高其速度。 糟糕! 只有在啟動(dòng)汽車(chē)后,我們才能提高速度并享受它發(fā)出的噪音。
現(xiàn)在,讓我們逐步了解一下汽車(chē)課:
- 使用class語(yǔ)句后跟類(lèi)名(Car)定義類(lèi)。 我們從冒號(hào)開(kāi)始縮進(jìn)代碼塊。
- 我們定義了兩個(gè)變量,速度和開(kāi)始。 這是此類(lèi)的所有實(shí)例將具有的數(shù)據(jù)。
- 接下來(lái),我們定義了對(duì)變量進(jìn)行操作的三種方法。
在這些方法的定義中,我們遇到了一些奇怪的事情:它們都有一個(gè)名為self的參數(shù)作為它們的第一個(gè)參數(shù)。
什么是Self?
老實(shí)說(shuō),如果您問(wèn)我,這是Python不太優(yōu)雅的語(yǔ)言構(gòu)造之一。
還記得我們?cè)谡{(diào)用car對(duì)象上的方法時(shí),例如car.start()嗎? 即使start被定義為類(lèi)中的start(self),我們也不必傳遞self變量。
這是正在發(fā)生的事情:
- 當(dāng)我們?cè)趯?duì)象上調(diào)用方法時(shí),Python會(huì)自動(dòng)填充第一個(gè)變量,我們習(xí)慣將其稱(chēng)為self
- 第一個(gè)變量是對(duì)對(duì)象本身的引用,因此它的名稱(chēng)
- 我們可以使用此變量來(lái)引用該對(duì)象的其他實(shí)例變量和函數(shù),例如self.speed和self.start()。
因此,僅在類(lèi)定義內(nèi)部,我們才使用self來(lái)引用屬于實(shí)例的變量。 要修改屬于我們課程一部分的開(kāi)始變量,我們使用self.started而不是僅僅啟動(dòng)。
通過(guò)使用self,我們可以很清楚地了解到我們正在對(duì)該實(shí)例進(jìn)行操作的變量,而不是在對(duì)象外部定義且碰巧具有相同名稱(chēng)的其他變量。
從一個(gè)類(lèi)創(chuàng)建多個(gè)對(duì)象
由于類(lèi)只是一個(gè)藍(lán)圖,因此您可以使用它來(lái)創(chuàng)建多個(gè)對(duì)象,就像可以制造多個(gè)外觀相同的汽車(chē)一樣。 它們的行為都相似,但是它們都有自己的數(shù)據(jù),這些數(shù)據(jù)不會(huì)在對(duì)象之間共享:
>>> car1 = Car()
>>> car2 = Car()
>>> id(car1)
139771129539104
>>> id(car2)
139771129539160
我們?cè)谶@里創(chuàng)建了兩個(gè)car對(duì)象car1和car2,并使用內(nèi)置方法id()來(lái)獲取它們的id。 Python中的每個(gè)對(duì)象都有一個(gè)唯一的標(biāo)識(shí)符,因此我們只是證明我們從同一類(lèi)創(chuàng)建了兩個(gè)不同的對(duì)象。 我們可以獨(dú)立使用它們:
>>> car1.start()
Car started, let's ride!
>>> car1.increase_speed(10)
'Vrooom!'
>>> car1.speed
10
>>> car2.speed
0
我們剛剛啟動(dòng)了car1并提高了速度,而car2仍然暫停。 檢查速度可以確認(rèn)這是狀態(tài)不同的不同汽車(chē)!
構(gòu)造函數(shù)
從類(lèi)創(chuàng)建對(duì)象時(shí),看起來(lái)我們正在調(diào)用一個(gè)函數(shù):
car = Car()
但這不只是看起來(lái)像我們?cè)谡{(diào)用函數(shù),實(shí)際上是在調(diào)用函數(shù)! 我們不必定義的此方法稱(chēng)為構(gòu)造函數(shù)。 它構(gòu)造并初始化對(duì)象。 默認(rèn)情況下,每個(gè)類(lèi)都有一個(gè)名為_(kāi)_init__的類(lèi),即使我們自己沒(méi)有定義它。 這與繼承有關(guān),您將很快了解。
您是否曾經(jīng)使用過(guò)str()函數(shù)將對(duì)象轉(zhuǎn)換為類(lèi)? 還是int()函數(shù)將字符串轉(zhuǎn)換為數(shù)字?
>>> 'a' + str(1)
'a1'
>>> int('2') + 2
4
您實(shí)際上在這里所做的就是通過(guò)調(diào)用str和int類(lèi)的構(gòu)造函數(shù)來(lái)創(chuàng)建類(lèi)型為str和int的新對(duì)象。
我們也可以重寫(xiě)__init__方法,以通過(guò)接受參數(shù)來(lái)賦予它更多的功能。 讓我們使用自定義構(gòu)造函數(shù)重新定義Car類(lèi):
- class Car:
- def __init__(self, started = False, speed = 0):
- self.started = started
- self.speed = speed
- def start(self):
- self.started = True
- print("Car started, let's ride!")
- def increase_speed(self, delta):
- if self.started:
- self.speed = self.speed + delta
- print("Vrooooom!")
- else:
- print("You need to start the car first")
- def stop(self):
- self.speed = 0
我們的自定義構(gòu)造函數(shù)已使用默認(rèn)值命名參數(shù),因此我們可以通過(guò)多種方式創(chuàng)建Car類(lèi)的實(shí)例:
>>> c1 = Car()
>>> c2 = Car(True)
>>> c3 = Car(True, 50)
>>> c4 = Car(started=True, speed=40)
您可能已經(jīng)注意到,我們現(xiàn)在可以創(chuàng)建未啟動(dòng)但仍要提高速度的新車(chē)。 現(xiàn)在,讓我們就這樣了。
繼承
在編程中,最好重用盡可能多的代碼。 這種做法甚至有一個(gè)很好的縮寫(xiě),叫做DRY:不要重復(fù)自己。
類(lèi)可以幫助您避免重復(fù)代碼,因?yàn)槟梢跃帉?xiě)一次類(lèi)并根據(jù)該類(lèi)創(chuàng)建許多對(duì)象。 但是,它們還以另一種方式(稱(chēng)為繼承)幫助您。 類(lèi)可以繼承其他類(lèi)的屬性和函數(shù),因此您不必重復(fù)自己的工作。
舉例來(lái)說(shuō),我們希望Car類(lèi)繼承Vehicle類(lèi)的一些基礎(chǔ)知識(shí)。 并且,在定義的同時(shí),還定義了Motorcycle類(lèi)。 從示意圖上看,它看起來(lái)像這樣:

> Inheritance — image by author
我們已經(jīng)看到繼承在起作用。 還記得我曾告訴您,即使您沒(méi)有定義一個(gè)類(lèi),每個(gè)類(lèi)都有一個(gè)構(gòu)造函數(shù)(init)嗎? 這是因?yàn)槊總€(gè)類(lèi)都繼承自Python中最基礎(chǔ)的類(lèi),即object:
>>> dir(object)
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
當(dāng)我告訴您" Python中的一切都是對(duì)象"時(shí),我的意思就是一切。 這包括類(lèi),并且您可以看到我們也可以在類(lèi)上使用dir()。 它表明該對(duì)象具有__init__方法。 不錯(cuò),不是嗎?
繼承映射到許多現(xiàn)實(shí)情況。 根據(jù)上圖,我們來(lái)看看繼承的作用。 我們將從通用的Vehicle類(lèi)開(kāi)始:
- class Vehicle:
- def __init__(self, started = False, speed = 0):
- self.started = started
- self.speed = speed
- def start(self):
- self.started = True
- print("Started, let's ride!")
- def stop(self):
- self.speed = 0
- def increase_speed(self, delta):
- if self.started:
- self.speed = self.speed + delta
- print("Vrooooom!")
- else:
- print("You need to start me first")
現(xiàn)在,我們可以使用繼承重新定義我們的Car類(lèi):
- class Car(Vehicle):
- trunk_open = False
- def open_trunk(self):
- trunk_open = True
- def close_trunk(self):
- trunk_open = False
我們的汽車(chē)?yán)^承了Vehicle類(lèi)的所有方法和變量,但添加了一個(gè)額外的變量和兩個(gè)方法來(lái)操作后備箱。
覆蓋init方法
有時(shí)您想覆蓋init函數(shù)。 為了演示,我們可以創(chuàng)建一個(gè)Motorcycle類(lèi)。 大多數(shù)摩托車(chē)都有中央支架。 我們將添加將其放入或初始化的功能:
- class Motorcycle(Vehicle):
- def __init__(self, center_stand_out = False):
- self.center_stand_out = center_stand_out
- super().__init__()
當(dāng)您重寫(xiě)構(gòu)造函數(shù)時(shí),根本不會(huì)調(diào)用父類(lèi)(我們從中繼承)的構(gòu)造函數(shù)。 如果仍然需要該功能,則必須自己調(diào)用它。 這是通過(guò)super()完成的:它返回對(duì)父類(lèi)的引用,因此我們可以調(diào)用父類(lèi)的構(gòu)造函數(shù)。
在這種情況下,我們?cè)黾恿酥兄弥Ъ艿墓δ?,但刪除了在構(gòu)造函數(shù)中設(shè)置速度和啟動(dòng)狀態(tài)的選項(xiàng)。 如果需要,您也可以添加速度和啟動(dòng)狀態(tài)選項(xiàng),并將其傳遞給Vehicle構(gòu)造函數(shù)。
覆蓋其他方法
就像__init__一樣,我們也可以覆蓋其他方法。 例如,如果您要實(shí)施不啟動(dòng)的摩托車(chē),則可以覆蓋啟動(dòng)方法:
- class Motorcycle(Vehicle):
- def __init__(self, center_stand_out = False):
- self.center_stand_out = center_stand_out
- super().__init__()
- def start(self):
- print("Sorry, out of fuel!")
感謝您的閱讀。 如果您想了解有關(guān)Python的更多信息,請(qǐng)確保在https://python3.guide上查看我的詳盡指南。