Python面向?qū)ο缶幊讨械念惡蛯?duì)象學(xué)習(xí)教程
Python中一切都是對(duì)象。類提供了創(chuàng)建新類型對(duì)象的機(jī)制。這篇教程中,我們不談?lì)惡兔嫦驅(qū)ο蟮幕局R(shí),而專注在更好地理解Python面向?qū)ο缶幊躺?。假設(shè)我們使用新風(fēng)格的python類,它們繼承自object父類。
定義類
class 語(yǔ)句可以定義一系列的屬性、變量、方法,他們被該類的實(shí)例對(duì)象所共享。下面給出一個(gè)簡(jiǎn)單類定義:
class Account(object): num_accounts = 0 def __init__(self, name, balance): self.name = name self.balance = balance Account.num_accounts += 1 def del_account(self): Account.num_accounts -= 1 def deposit(self, amt): self.balance = self.balance + amt def withdraw(self, amt): self.balance = self.balance - amt def inquiry(self): return self.balance
類定義引入了以下新對(duì)象:
類對(duì)象
實(shí)例對(duì)象
方法對(duì)象
類對(duì)象
程序執(zhí)行過(guò)程中遇到類定義時(shí),就會(huì)創(chuàng)建新的命名空間,命名空間包含所有類變量和方法定義的名稱綁定。注意該命名空間并沒(méi)有創(chuàng)建類方法可以使用的新局部作用域,因此在方法中訪問(wèn)變量需要全限定名稱。上一節(jié)的Account類演示了該特性;嘗試訪問(wèn)num_of_accounts變量的方法需要使用全限定名稱Account.num_of_accounts,否則,如果沒(méi)有在__init__方法中使用全限定名稱,會(huì)引發(fā)如下錯(cuò)誤:
class Account(object): num_accounts = 0 def __init__(self, name, balance): self.name = name self.balance = balance num_accounts += 1 def del_account(self): Account.num_accounts -= 1 def deposit(self, amt): self.balance = self.balance + amt def withdraw(self, amt): self.balance = self.balance - amt def inquiry(self): return self.balance >>> acct = Account('obi', 10) Traceback (most recent call last): File "python", line 1, in <module> File "python", line 9, in __init__ UnboundLocalError: local variable 'num_accounts' referenced before assignment
類定義執(zhí)行的最后,會(huì)創(chuàng)建一個(gè)類對(duì)象。在進(jìn)入類定義之前有效的那個(gè)作用域現(xiàn)在被恢復(fù)了,同時(shí)類對(duì)象被綁定到類定義頭的類名上。
先偏離下話題,你可能會(huì)問(wèn)如果創(chuàng)建的類是對(duì)象,那么類對(duì)象的類是什么呢?。與一切都是對(duì)象的python哲學(xué)一致,類對(duì)象確實(shí)有個(gè)類,即python新風(fēng)格類中的type類。
>>> type(Account) <class 'type'>
讓你更迷惑一點(diǎn),Account類型的類型是type。type類是個(gè)元類,用于創(chuàng)建其他類,我們稍后教程中再介紹。
類對(duì)象支持屬性引用和實(shí)例化。屬性通過(guò)標(biāo)準(zhǔn)的點(diǎn)語(yǔ)法引用,即對(duì)象后跟句點(diǎn),然后是屬性名:obj.name。有效的屬性名是類對(duì)象創(chuàng)建后類命名空間中出現(xiàn)的所有變量和方法名。例如:
>>> Account.num_accounts >>> 0 >>> Account.deposit >>> <unbound method Account.deposit>
類實(shí)例化使用函數(shù)表示法。實(shí)例化會(huì)像普通函數(shù)一樣無(wú)參數(shù)調(diào)用類對(duì)象,如下文中的Account類:
>>> Account()
類對(duì)象實(shí)例化之后,會(huì)返回實(shí)例對(duì)象,如果類中定義了__init__方法,就會(huì)調(diào)用,實(shí)例對(duì)象作為第一個(gè)參數(shù)傳遞過(guò)去。這個(gè)方法會(huì)進(jìn)行用戶自定義的初始化過(guò)程,比如實(shí)例變量的初始化。Account類為例,賬戶name和balance會(huì)被設(shè)置,實(shí)例對(duì)象的數(shù)目增加1。
實(shí)例對(duì)象
如果類對(duì)象是餅干切割刀,餅干就是實(shí)例化類對(duì)象的結(jié)果。實(shí)例對(duì)象上的全部有效操作為對(duì)屬性、數(shù)據(jù)和方法對(duì)象的引用。
方法對(duì)象
方法對(duì)象和函數(shù)對(duì)象類似。如果x是Account類的實(shí)例,x.deposit就是方法對(duì)象的例子。方法定義中有個(gè)附加參數(shù),self。self指向類實(shí)例。為什么我們需要把實(shí)例作為參數(shù)傳遞給方法?方法調(diào)用能最好地說(shuō)明:
>>> x = Account() >>> x.inquiry() 10
實(shí)例方法調(diào)用時(shí)發(fā)生了什么?你應(yīng)該注意到x.inquiry()調(diào)用時(shí)沒(méi)有參數(shù),雖然方法定義包含self參數(shù)。那么這個(gè)參數(shù)到底發(fā)生了什么?
特殊之處在于方法所作用的對(duì)象被作為函數(shù)的第一個(gè)參數(shù)傳遞過(guò)去。在我們的例子中,對(duì)x.inquiry()的調(diào)用等價(jià)于Account.f(x)。一般,調(diào)用n參數(shù)的方法等同于將方法的作用對(duì)象插入到第一個(gè)參數(shù)位置。
python教程上講:
當(dāng)引用的實(shí)例屬性不是數(shù)據(jù)屬性時(shí),就會(huì)搜索類。如果名稱表示一個(gè)合法的函數(shù)對(duì)象,實(shí)例對(duì)象和函數(shù)對(duì)象將會(huì)被打包到一個(gè)抽象對(duì)象,即方法對(duì)象中。包含參數(shù)列表的方法對(duì)象被調(diào)用時(shí),將會(huì)根據(jù)實(shí)例對(duì)象和參數(shù)列表創(chuàng)建一個(gè)新的參數(shù)列表,然后函數(shù)對(duì)象將會(huì)使用新的參數(shù)列表被調(diào)用。
這適用于所有的實(shí)例方法對(duì)象,包括__init__方法。self參數(shù)其實(shí)不是一個(gè)關(guān)鍵字,任何有效的參數(shù)名都可以使用,如下Account類定義所示:
class Account(object): num_accounts = 0 def __init__(obj, name, balance): obj.name = name obj.balance = balance Account.num_accounts += 1 def del_account(obj): Account.num_accounts -= 1 def deposit(obj, amt): obj.balance = obj.balance + amt def withdraw(obj, amt): obj.balance = obj.balance - amt def inquiry(obj): return obj.balance >>> Account.num_accounts >>> 0 >>> x = Account('obi', 0) >>> x.deposit(10) >>> Account.inquiry(x) >>> 10
靜態(tài)和類方法
類中定義的方法默認(rèn)由實(shí)例調(diào)用。但是,我們也可以通過(guò)對(duì)應(yīng)的@staticmethod和@classmethod裝飾器來(lái)定義靜態(tài)或類方法。
靜態(tài)方法
靜態(tài)方式是類命名空間中的普通函數(shù)。引用類的靜態(tài)方法返回的是函數(shù)類型,而不是非綁定方法類型:
class Account(object): num_accounts = 0 def __init__(self, name, balance): self.name = name self.balance = balance Account.num_accounts += 1 def del_account(self): Account.num_accounts -= 1 def deposit(self, amt): self.balance = self.balance + amt def withdraw(self, amt): self.balance = self.balance - amt def inquiry(self): return "Name={}, balance={}".format(self.name, self.balance) @staticmethod def type(): return "Current Account" >>> Account.deposit <unbound method Account.deposit> >>> Account.type <function type at 0x106893668>
使用@staticmethod裝飾器來(lái)定義靜態(tài)方法,這些方法不需要self參數(shù)。靜態(tài)方法可以更好地組織與類相關(guān)的代碼,也可以在子類中被重寫(xiě)。
類方法
類方法由類自身來(lái)調(diào)用,而不是實(shí)例。類方法使用@classmethod裝飾器定義,作為第一個(gè)參數(shù)被傳遞給方法的是類而不是實(shí)例。
import json class Account(object): num_accounts = 0 def __init__(self, name, balance): self.name = name self.balance = balance Account.num_accounts += 1 def del_account(self): Account.num_accounts -= 1 def deposit(self, amt): self.balance = self.balance + amt def withdraw(self, amt): self.balance = self.balance - amt def inquiry(self): return "Name={}, balance={}".format(self.name, self.balance) @classmethod def from_json(cls, params_json): params = json.loads(params_json) return cls(params.get("name"), params.get("balance")) @staticmethod def type(): return "Current Account"
類方法一個(gè)常見(jiàn)的用法是作為對(duì)象創(chuàng)建的工廠。假如Account類的數(shù)據(jù)格式有很多種,比如元組、json字符串等。由于Python類只能定義一個(gè)__init__方法,所以類方法在這些情形中就很方便。以上文Account類為例,我們想根據(jù)一個(gè)json字符串對(duì)象來(lái)初始化一個(gè)賬戶,我們定義一個(gè)類工廠方法from_json,它讀取json字符串對(duì)象,解析參數(shù),根據(jù)參數(shù)創(chuàng)建賬戶對(duì)象。另一個(gè)類實(shí)例的例子是dict.fromkeys 方法,它從一組鍵和值序列中創(chuàng)建dict對(duì)象。
Python特殊方法
有時(shí)我們希望自定義類。這需要改變類對(duì)象創(chuàng)建和初始化的方法,或者對(duì)某些操作提供多態(tài)行為。多態(tài)行為允許定制在類定義中某些如+等python操作的自身實(shí)現(xiàn)。Python的特殊方法可以做到這些。這些方法一般都是__*__形式,其中*表示方法名。如__init__和__new__來(lái)自定義對(duì)象創(chuàng)建和初始化,__getitem__、__get__、__add__、__sub__來(lái)模擬python內(nèi)建類型,還有__getattribute__、__getattr__等來(lái)定制屬性訪問(wèn)。只有為數(shù)不多的特殊方法,我們討論一些重要的特殊方法來(lái)做個(gè)簡(jiǎn)單理解,python文檔有全部方法的列表。
進(jìn)行對(duì)象創(chuàng)建的特殊方法
新的類實(shí)例通過(guò)兩階段過(guò)程創(chuàng)建,__new__方法創(chuàng)建新實(shí)例,__init__初始化該實(shí)例。用戶已經(jīng)很熟悉__init__方法的定義;但用戶很少定義__new__方法,但是如果想自定義類實(shí)例的創(chuàng)建,也是可以的。
屬性訪問(wèn)的特殊方法
我們可以通過(guò)實(shí)現(xiàn)以下方法來(lái)定制類實(shí)例的屬性訪問(wèn)。
class Account(object): num_accounts = 0 def __init__(self, name, balance): self.name = name self.balance = balance Account.num_accounts += 1 def del_account(self): Account.num_accounts -= 1 def __getattr__(self, name): return "Hey I dont see any attribute called {}".format(name) def deposit(self, amt): self.balance = self.balance + amt def withdraw(self, amt): self.balance = self.balance - amt def inquiry(self): return "Name={}, balance={}".format(self.name, self.balance) @classmethod def from_dict(cls, params): params_dict = json.loads(params) return cls(params_dict.get("name"), params_dict.get("balance")) @staticmethod def type(): return "Current Account" x = Account('obi', 0)
__getattr__(self, name)__:這個(gè)方法只有當(dāng)name既不是實(shí)例屬性也不能在對(duì)象的類繼承鏈中找到時(shí)才會(huì)被調(diào)用。這個(gè)方法應(yīng)當(dāng)返回屬性值或者引發(fā)AttributeError異常。例如,如果x是Account類的實(shí)例,嘗試訪問(wèn)不存在的屬性將會(huì)調(diào)用這個(gè)方法。
>>> acct = Account("obi", 10) >>> acct.number Hey I dont see any attribute called number
注意如果 __getattr__引用不存在的實(shí)例屬性,可能會(huì)發(fā)生死循環(huán),因?yàn)開(kāi)_getattr__方法不斷被調(diào)用。
2.__setattr__(self, name, value)__:這個(gè)方法當(dāng)屬性賦值發(fā)生時(shí)調(diào)用。__setattr__將會(huì)把值插入到實(shí)例屬性字典中,而不是使用self.name=value,因?yàn)樗鼤?huì)導(dǎo)致遞歸調(diào)用的死循環(huán)。
3.__delattr__(self, name)__:del obj發(fā)生時(shí)調(diào)用。
4.__getattribute__(self, name)__:這個(gè)方法會(huì)被一直調(diào)用以實(shí)現(xiàn)類實(shí)例的屬性訪問(wèn)。
類型模擬的特殊方法
對(duì)某些類型,Python定義了某些特定語(yǔ)法;比如,列表和元組的元素可以通過(guò)索引表示法來(lái)訪問(wèn),數(shù)值可以通過(guò)+操作符來(lái)進(jìn)行加法等等。我們可以創(chuàng)建自己的使用這些特殊語(yǔ)法的類,python解釋器遇到這些特殊語(yǔ)法時(shí)就會(huì)調(diào)用我們實(shí)現(xiàn)的方法。我們?cè)谙旅嬗靡粋€(gè)簡(jiǎn)單的例子來(lái)演示這個(gè)特性,它模擬python列表的基本用法。
class CustomList(object): def __init__(self, container=None): # the class is just a wrapper around another list to # illustrate special methods if container is None: self.container = [] else: self.container = container def __len__(self): # called when a user calls len(CustomList instance) return len(self.container) def __getitem__(self, index): # called when a user uses square brackets for indexing return self.container[index] def __setitem__(self, index, value): # called when a user performs an index assignment if index <= len(self.container): self.container[index] = value else: raise IndexError() def __contains__(self, value): # called when the user uses the 'in' keyword return value in self.container def append(self, value): self.container.append(value) def __repr__(self): return str(self.container) def __add__(self, otherList): # provides support for the use of the + operator return CustomList(self.container + otherList.container)
上面,CustomList是個(gè)真實(shí)列表的簡(jiǎn)單包裝器。我們?yōu)榱搜菔緦?shí)現(xiàn)了一些自定義方法:
__len__(self):對(duì)CustomList實(shí)例調(diào)用len()函數(shù)時(shí)被調(diào)用。
>>> myList = CustomList() >>> myList.append(1) >>> myList.append(2) >>> myList.append(3) >>> myList.append(4) >>> len(myList) 4
2.__getitem__(self, value):提供CustomList類實(shí)例的方括號(hào)索引用法支持:
>>> myList = CustomList() >>> myList.append(1) >>> myList.append(2) >>> myList.append(3) >>> myList.append(4) >>> myList[3] 4
3.__setitem__(self, key, value):當(dāng)對(duì)CustomList類實(shí)例上self[key]賦值時(shí)調(diào)用。
>>> myList = CustomList() >>> myList.append(1) >>> myList.append(2) >>> myList.append(3) >>> myList.append(4) >>> myList[3] = 100 4 >>> myList[3] 100
4.__contains__(self, key):成員檢測(cè)時(shí)調(diào)用。如果包含該項(xiàng)就返回true,否則false。
>>> myList = CustomList() >>> myList.append(1) >>> myList.append(2) >>> myList.append(3) >>> myList.append(4) >>> 4 in myList True
5.__repr__(self):當(dāng)用print打印self時(shí)調(diào)用,將會(huì)打印self的對(duì)象表示。
>>> myList = CustomList() >>> myList.append(1) >>> myList.append(2) >>> myList.append(3) >>> myList.append(4) >>> print myList [1, 2, 3, 4]
6.__add__(self, otherList):使用+操作符來(lái)計(jì)算兩個(gè)CustomList實(shí)例相加時(shí)調(diào)用。
>>> myList = CustomList() >>> otherList = CustomList() >>> otherList.append(100) >>> myList.append(1) >>> myList.append(2) >>> myList.append(3) >>> myList.append(4) >>> myList + otherList + otherList [1, 2, 3, 4, 100, 100]
上面的例子演示了如何通過(guò)定義某些特殊類方法來(lái)定制類行為??梢栽?a target="_blank" >Python文檔中查看這些自定義方法的完整列表。在接下來(lái)的教程中,我們會(huì)將特殊方法放到一起來(lái)討論,并解釋描述符這個(gè)在python面向?qū)ο缶幊讨袕V泛使用的重要功能。
相關(guān)文章
Python 利用base64庫(kù) 解碼本地txt文本字符串
這篇文章主要介紹了Python 利用base64庫(kù) 解碼本地txt文本字符串的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-04-04python3.7中安裝paddleocr及paddlepaddle包的多種方法
這篇文章主要介紹了python3.7中安裝paddleocr及paddlepaddle包,本文通過(guò)多種方法給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-11-11python初步實(shí)現(xiàn)word2vec操作
這篇文章主要介紹了python初步實(shí)現(xiàn)word2vec操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-06-06Python利用pdfplumber庫(kù)提取pdf中的文字
pdfplumber是一個(gè)用于從PDF文檔中提取文本和表格數(shù)據(jù)的Python庫(kù),它可以幫助用戶輕松地從PDF文件中提取有用的信息,例如表格、文本、元數(shù)據(jù)等,本文將給大家介紹如何通過(guò)Python的pdfplumber庫(kù)提取pdf中的文字,需要的朋友可以參考下2023-05-05Python實(shí)現(xiàn)批量識(shí)別圖片文字并存為Excel
批量文字識(shí)別是Python辦公自動(dòng)化的基本操作,應(yīng)用在我們工作生活中的方方面面。本文主要以開(kāi)源免費(fèi)的easyocr來(lái)實(shí)現(xiàn)批量識(shí)別圖片文字并存為Excel,感興趣的可以學(xué)習(xí)一下2022-06-06簡(jiǎn)單了解python中的f.b.u.r函數(shù)
這篇文章主要介紹了簡(jiǎn)單了解python中的f.b.u.r函數(shù),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11Python Pygame實(shí)戰(zhàn)之飛機(jī)大戰(zhàn)的實(shí)現(xiàn)
飛機(jī)大戰(zhàn)想必是很多人童年時(shí)期的經(jīng)典游戲,這篇文章主要給大家介紹了關(guān)于如何利用python中的Pygame模塊寫(xiě)一個(gè)簡(jiǎn)單的飛機(jī)大戰(zhàn)小游戲的相關(guān)資料,需要的朋友可以參考下2022-03-03使用Django+Vue編寫(xiě)一個(gè)文件上傳器
本教程中,我將會(huì)使用Django和Vue創(chuàng)建一個(gè)支持多文件上傳的文件上傳器,您可以在單個(gè)文件和多個(gè)文件上傳之間選擇在應(yīng)用中進(jìn)行上傳操作,文章通過(guò)代碼示例截殺的非常詳細(xì),需要的朋友可以參考下2023-11-11