在Python中,首字母大寫的名稱指的是類。這個類定義中的括弧是空的,因為我們要從空白建立這個類。我們編寫了一個文檔字串,對這個類的功能作了描述。類中的函數稱為
方法。
以Student類為例,在Python中,定義類是通過class關鍵字:
class Student(object): pass |
class後面緊接著是類名,即Student,類名通常是大寫開頭的單詞,緊接著是(object),表示該類是從哪個類繼承下來的,通常,如果沒有合適的繼承類,就使用object類,這是所有類最終都會繼承的類。
9.1.1建立類
class Student(object): def __init__(self, name, score): self.name = name self.score = score |
1.方法__init__() 是一個特殊的方法,建立新執行個體時,Python都會自動運行它。開頭和末尾各有兩個底線,這是一種約定,旨在避免Python預設方法與普通方法發生名稱衝突。這個方法的定義中,形參self 必不可少,還必須位於其他形參的前面。
2.每個與類相關聯的方法調用都自動傳遞實參self ,它是一個指向執行個體本身的引用,讓執行個體能夠訪問類中的屬性和方法。self 會自動傳遞,因此我們不需要傳遞它。
3.以self 為首碼的變數都可供類中的所有方法使用,我們還可以通過類的任何執行個體來訪問這些變數。
4.self.name= name像這樣可通過執行個體訪問的變數稱為屬性
5.物件導向編程的一個重要特點就是資料封裝。可以直接在類的內部定義訪問資料的函數,這樣,就把“資料”給封裝起來了。這些封裝資料的函數是和Student類本身是關聯起來的,我們稱之為類的方法.
9.1.2根據類建立執行個體
我們通常可以認為首字母大寫的名稱(如Dog )指的是類,而 小寫名稱(如my_dog )指的是根據類建立的執行個體。
1、要訪問執行個體的屬性,可使用句點標記法,我們編寫了如下代碼來訪問my_dog 的屬性name 的值。
my_dog.name
句點標記法在Python中很常用,這種文法示範了Python如何獲悉屬性的值。
2、根據Dog 類建立執行個體後,就可以使用句點標記法來調用Dog 類中定義的任何方法。
3、可按需求根據類建立任意數量的執行個體。
9.2使用類和執行個體
1.你需要執行的一個重要任務是修改執行個體的屬性。你可以直接修改執行個體的屬性,也可以編寫方法以特定的方式進行修改。
2.類是建立執行個體的模板,而執行個體則是一個一個具體的對象,各個執行個體擁有的資料都互相獨立,互不影響;方法就是與執行個體綁定的函數,和普通函數不同,方法可以直接存取執行個體的資料;通過在執行個體上調用方法,我們就直接操作了對象內部的資料,但無需知道方法內部的實現細節。和靜態語言不同,Python允許對執行個體變數綁定任何資料,也就是說,對於兩個執行個體變數,雖然它們都是同一個類的不同執行個體,但擁有的變數名稱都可能不同。
9.2.1給類設定初始值
類中的每個屬性都必須有初始值,哪怕這個值是0或Null 字元串。在有些情況下,如設定預設值時,在方法__init__() 內指定這種初始值是可行的;如果你對某個屬性這樣做了,就無需包含為它提供初始值的形參。
直接在class中定義屬性,這種屬性是類屬性:
class Student(object): name = 'Student' |
在編寫程式的時候,千萬不要把執行個體屬性和類屬性使用相同的名字,因為相同名稱的執行個體屬性將屏蔽掉類屬性,但是當你刪除執行個體屬性後,再使用相同的名稱,訪問到的將是類屬性。
9.2.2修改屬性的值
可以以三種不同的方式修改屬性的值:
1.直接通過執行個體進行修改;
2.通過方法進行設定;
3.通過方法進行遞增(增加特定的值)。
9.2.3訪問限制
1.在Class內部,可以有屬性和方法,而外部代碼可以通過直接調用執行個體變數的方法來操作資料,這樣,就隱藏了內部的複雜邏輯。
2.如果要讓內部屬性不被外部存取,可以把屬性的名稱前加上兩個底線__,在Python中,執行個體的變數名如果以__開頭,就變成了一個私人變數(private),只有內部可以訪問,外部不能訪問。
class Student(object): def __init__(self, name, score): self.__name = name self.__score = score def print_score(self): print('%s: %s' % (self.__name, self.__score)) |
3.改完後,對於外部代碼來說,沒什麼變動,但是已經無法從外部存取執行個體變數.__name和執行個體變數.__score了:
>>> bart = Student('Bart Simpson', 98) >>> bart.__name Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Student' object has no attribute '__name' |
4.這樣就確保了外部代碼不能隨意修改對象內部的狀態,這樣通過訪問限制的保護,代碼更加健壯。但是如果外部代碼要擷取name和score,可以給Student類增加get_name和get_score這樣的方法:
class Student(object): ... def get_name(self): return self.__name def get_score(self): return self.__score |
5.如果又要允許外部代碼修改score,可以再給Student類增加set_score方法:
class Student(object): ... def set_score(self, score): self.__score = score |
6.和原來直接調用參數相比,在方法中,可以對參數做檢查,避免傳入無效的參數:
class Student(object): ... def set_score(self, score): if 0 <= score <= 100: self.__score = score else: raise ValueError('bad score') |
7.需要注意的是,在Python中,變數名類似__xxx__的,也就是以雙底線開頭,並且以雙底線結尾的,是特殊變數,特殊變數是可以直接存取的,不是private變數,所以,不能用__name__、__score__這樣的變數名。有些時候,你會看到以一個底線開頭的執行個體變數名,比如_name,這樣的執行個體變數外部是可以訪問的,但是,按照約定俗成的規定,當你看到這樣的變數時,意思就是,“雖然我可以被訪問,但是,請把我視為私人變數,不要隨意訪問”。
8.雙底線開頭的執行個體變數也不是一定不能從外部存取。不能直接存取__name是因為Python解譯器對外把__name變數改成了_Student__name,所以,仍然可以通過_Student__name來訪問__name變數。
9.3繼承
1.如果一個類似另一個類的特殊版本,可以使用繼承,一個類繼承另一個類它將自動擷取另一個類的所有屬性和方法。原有的類為父類,新類為子類。
2.子類繼承父類所有的屬性和方法,還可以定義自己的屬於和方法。在OOP程式設計中,當我們定義一個class的時候,可以從某個現有的class繼承,新的class稱為子類(Subclass),而被繼承的class稱為基類、父類或超類(Base class、Super class)。
class Dog(Animal): #繼承Animal pass |
9.3.1子類的方法__init__()
1.繼承需要給父類的所有屬性賦值,子類的__init__()需要父類施以援手。
2.並且父類必須在繼承檔案中,在子類之前。
3.定義子類時,必須在括弧中指定父類的名稱。
4.super()特殊函數,協助python將父類和子類並聯起來。父類也稱超類,super的由來。
9.3.2定義子類的方法和屬性
讓一個類繼承一個類後。可添加區別子類和父類的屬性和方法。
9.3.3重寫父類
對應父類的方法,只有不符合子類的需要都可以重寫,在子類中添加新的方法描述子類的特點。去父類的去其糟怕取其精華。
9.3.4多態
1.當子類和父類都存在相同的方法時,我們說,子類覆蓋了父類的方法,在代碼啟動並執行時候,總是會調用子類的方法。這樣,我們就獲得了繼承的另一個好處:多態
2.所以,在繼承關係中,如果一個執行個體的資料類型是某個子類,那它的資料類型也可以被看做是父類。但是,反過來就不行。
3.多態的好處是,當我們需要傳入Dog、Cat、Tortoise……時,我們只需要接收Animal類型就可以了,因為Dog、Cat、Tortoise……都是Animal類型,然後,按照Animal類型進行操作即可。由於Animal類型有run()方法,因此,傳入的任意類型,只要是Animal類或者子類,就會自動調用實際類型的run()方法,這就是多態的意思。
4.對於一個變數,我們只需要知道它是Animal類型,無需確切地知道它的子類型,就可以放心地調用run()方法,而具體調用的run()方法是作用在Animal、Dog、Cat還是Tortoise對象上,由運行時該對象的確切類型決定,這就是多態真正的威力:調用方只管調用,不管細節,而當我們新增一種Animal的子類時,只要確保run()方法編寫正確,不用管原來的代碼是如何調用的。這就是著名的“開閉”原則:
9.3.5使用__slots__
為了達到限制的目的,Python允許在定義class的時候,定義一個特殊的__slots__變數,來限制該class執行個體能添加的屬性。
class Student(object): __slots__ = ('name', 'age') # 用tuple定義允許綁定的屬性名稱 |
使用__slots__要注意,__slots__定義的屬性僅對當前類執行個體起作用,對繼承的子類是不起作用的。
9.3.6多重繼承
1.通過多重繼承,一個子類就可以同時獲得多個父類的所有功能。
2.設計類的繼承關係時,通常,主線都是單一繼承下來的,例如,Ostrich(鴕鳥)繼承自Bird。但是,如果需要“混入”額外的功能,通過多重繼承就可以實現,比如,讓Ostrich除了繼承自Bird外,再同時繼承Runnable。這種設計通常稱之為MixIn。MixIn的目的就是給一個類增加多個功能,這樣,在設計類的時候,我們優先考慮通過多重繼承來組合多個MixIn的功能,而不是設計多層次的複雜的繼承關係。
3.這樣我們就不需要複雜而龐大的繼承鏈,只要選擇組合不同的類的功能,就可以快速構造出所需的子類。由於Python允許使用多重繼承,因此,MixIn就是一種常見的設計。只允許單一繼承的語言(如Java)不能使用MixIn的設計。
9.3.7定製類
1.Python的class中還有許多這樣有特殊用途的函數,可以協助我們定製類。
__str__
定義好__str__()方法,可以返回一個好看的字串就:
>>> class Student(object): ... def __init__(self, name): ... self.name = name ... def __str__(self): ... return 'Student object (name: %s)' % self.name ... >>> print(Student('Michael')) Student object (name: Michael) |
這樣列印出來的執行個體,不但好看,而且容易看出執行個體內部重要的資料。
直接敲變數不用print,列印出來的執行個體還是不好看:
>>> s = Student('Michael') >>> s <__main__.Student object at 0x109afb310> |
這是因為直接顯示變數調用的不是__str__(),而是__repr__(),兩者的區別是__str__()返回使用者看到的字串,而__repr__()返回程式開發人員看到的字串,也就是說,__repr__()是為調試服務的。
解決辦法是再定義一個__repr__()。但是通常__str__()和__repr__()代碼都是一樣的,所以,有個偷懶的寫法:
class Student(object): def __init__(self, name): self.name = name def __str__(self): return 'Student object (name=%s)' % self.name __repr__ = __str__ |
__iter__
如果一個類想被用於for ... in迴圈,類似list或tuple那樣,就必須實現一個__iter__()方法,該方法返回一個迭代對象,然後,Python的for迴圈就會不斷調用該迭代對象的__next__()方法拿到迴圈的下一個值,直到遇到StopIteration錯誤時退出迴圈。
我們以斐波那契數列為例,寫一個Fib類,可以作用於for迴圈:
class Fib(object): def __init__(self): self.a, self.b = 0, 1 # 初始化兩個計數器a,b def __iter__(self): return self # 執行個體本身就是迭代對象,故返回自己 def __next__(self): self.a, self.b = self.b, self.a + self.b # 計算下一個值 if self.a > 100000: # 退出迴圈的條件 raise StopIteration() return self.a # 返回下一個值 |
現在,試試把Fib執行個體作用於for迴圈:
>>> for n in Fib(): ... print(n) ... 1 1 2 3 5 ... 46368 75025 |
__getitem__
Fib執行個體雖然能作用於for迴圈,看起來和list有點像,但是,把它當成list來使用還是不行,比如,取第5個元素:
>>> Fib()[5] Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'Fib' object does not support indexing |
要表現得像list那樣按照下標取出元素,需要實現__getitem__()方法:
class Fib(object): def __getitem__(self, n): a, b = 1, 1 for x in range(n): a, b = b, a + b return a |
__getattr__
Python還有另一個機制,那就是寫一個__getattr__()方法,動態返回一個屬性。當調用不存在的屬性時,比如score,Python解譯器會試圖調用__getattr__(self, 'score')來嘗試獲得屬性,這樣,我們就有機會返回score的值。已有的屬性,比如name,不會在__getattr__中尋找。
__call__
1.任何類,只需要定義一個__call__()方法,就可以直接對執行個體進行調用。
2.通過callable()函數,我們就可以判斷一個對象是否是“可調用”對象。
9.3.8枚舉類
為這樣的枚舉類型定義一個class類型,然後,每個常量都是class的一個唯一執行個體。Python提供了Enum類來實現這個功能:
from enum import Enum Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec')) |
9.3.9元類
type()
要建立一個class對象,type()函數依次傳入3個參數:
class的名稱;
繼承的父類集合,注意Python支援多重繼承,如果只有一個父類,別忘了tuple的單元素寫法;
class的方法名稱與函數綁定,這裡我們把函數fn綁定到方法名hello上。
metaclass
metaclass,直譯為元類,簡單的解釋就是:當我們定義了類以後,就可以根據這個類建立出執行個體,所以:先定義類,然後建立執行個體。但是如果我們想建立出類呢?那就必鬚根據metaclass建立出類,所以:先定義metaclass,然後建立類。串連起來就是:先定義metaclass,就可以建立類,最後建立執行個體。
__new__()方法接收到的參數依次是:
當前準備建立的類的對象;
類的名字;
類繼承的父類集合;
類的方法集合。
9.3.10將執行個體用作屬性
使用代碼類比實物時,當你添加越來越多的特點和細節是,會發現屬性和方法以及代碼檔案都越來越長,這時候可以把其中一部分分離,重新組成一個類。可以將一個大類分成許多小類。
9.3.11類比實物
類比較複雜的實物,需要從較高的邏輯層次考慮。為了編寫效率更高,更簡潔準確的代碼,可能需要不停的重組類。
9.4匯入類
1.隨著不斷對類的添加,即使很優秀的繼承也會是代碼變得很長,所以python可以讓你把類匯入模組。並在主程式中匯入使用所需要的模組。
2.為自己的每個模組寫文檔字串,說明模組的作用,對內容進行簡要敘述。
3.從一個模組匯入多個類,用逗號隔開,並根據自己的需要建立執行個體。
4.也可以匯入整個類,並在執行個體前加上類名,和訪問需要的類。
5.在一個模組中匯入另一個模組。
9.5python標準庫(模組)
Python標準庫是一組模組,只需要import語句就可以匯入使用。
字典讓你把資訊關聯起來,但不記錄你添加索引值對的順序,要建立字典並記錄添加索引值對的順序,可使用模組collections中的orderedDict類。OrderedDict的執行個體和字典一樣,不過記錄了添加索引值對的添加順序。
OrderdDict很不錯,兼具列表和字典的特點,有時候很需要。
random模組
包含各種方式產生隨機數的函數。其中randint()返回一個位於指定範圍內的整數。
datetime是模組
datetime模組還包含一個datetime類,通過from datetime import datetime匯入的才是datetime這個類。如果僅匯入import datetime,則必須引用全名datetime.datetime。
datetime.now()返回當前日期和時間,其類型是datetime。
常用的第三方庫
還有MySQL的驅動:mysql-connector-python,用於科學計算的NumPy庫:numpy,用於產生文本的模板工具Jinja2,等等。
1.urlparse 模組,urlpasrse 模組提供了一些準系統,用於處理 URL 字串。這些功能包括 urlparse()、 urlunparse()和 urljoin()。
2.urlparse()將 urlstr 解析成一個 6 元組(prot_sch, net_loc, path, params, query, frag)。前面已 經描述了這裡的每個組件。
3.urlunparse()的功能與 urlpase()完全相反,其將經 urlparse()處理的 URL 產生 urltup 這個 6 元組(prot_sch, net_loc, path, params, query, frag),拼接成 URL 並返回。
4.urllib 模組提供了許多函數,可用於從指定 URL 下載資料,同時也可以對字串進行編 碼、解碼工作,以便在 URL 中以正確的形式顯示出來。下面將要介紹的函數包括 urlopen()、 urlretrieve()、quote()、unquote()、quote_plus()、unquote_plus()和 urlencode()。
5.urlopen()開啟一個給定 URL 字串表示的 Web 連線,並返迴文件類型的對象。
6.urlretrieve()不是用來以檔案的形式訪問並開啟 URL,而是用於下載完整的 HTML,把另存新檔檔案。
9.6類編碼風格
熟悉與類相關的編碼風格,當程式複雜時更應該這樣。
1.類名應採用駝峰命名法,即類的首字母大寫,不含底線,而執行個體名和模組名都首字母小寫,並用底線串連單詞。
2.在每個類定義後面添加文檔字串說明,簡要描述類的功能作用,並且在每個模組下面都要添加文檔字串說明,對模組下的類可以做什麼進行簡要說明。
3.用空行來組織代碼,但不要濫用,在類中可用一個空行分隔方法,用兩個空行分隔類。
4.需要同時匯入標準庫中的模組和自己編寫的模組時,先編寫匯入標準庫模組的import語句,用一個空行隔開,在匯入自己編寫的模組import語句。在包含多個import語句時,可以讓人容易弄明白程式中各個模組的由來。