Python是物件導向的高階程式語言,在Python裡面“一切都是物件”:數字、字串、元組、列表、字典、集合等內建資料型別,以及函式、方法、類、模組都是物件。
語言本身提供了上述的基本物件,但在實際程式設計中,我們要創造各種各樣的物件,Python就為我們提供了創造我們自己的物件的方法:類。
類(Class),就是組合資料和功能的方法,它讓我們建立一個新型別的物件,並可以建立該型別的新例項。類組合的資料,就是儲存自己狀態的屬性,而它組合的功能(函式)就是改變自己狀態的(定義在類中的)方法。類內部定義的函式,稱為類的方法。
Python中的類和其它語言(比如C++)有很多相似的特徵但也有些區別。如果你已瞭解其它語言的類的概念,可以在學習Python類時做一定的對比進行學習;如果你沒有學過其它語言也不要緊,學過之後你會發現,類的概念是如此簡單。
類的定義
類的定義是透過關鍵字class
實現的,下面是最簡單的類的定義的樣子:
class ClassName:
語句1
...
語句n
是不是這個形式跟函式的定義(def 語句)很像。因為類是資料和功能的組合,所以語句1
可能是內部變數(資料)的定義和賦值語句,也可能是內部方法(函式)的定義語句。類內部的函式定義通常具有一種特別形式的引數列表,這是方法呼叫的約定規範裡面指明的。這個特別形式就是第一個引數必須是self
,後面將詳細介紹。
進入類定義時,就會建立一個新的名稱空間,並把它用作區域性作用域。因此,所有對區域性變數的賦值都是在這個新名稱空間內進行的。特別的,函式定義會繫結到這個區域性作用域裡的新函式名稱。
正常離開(從結尾出)類定義時,就會建立一個類物件。它基本上是一個包圍在類定義所建立的名稱空間內容周圍的包裝器。元素的(在進入類定義之前起作用的)區域性作用域將重新生效,類物件將在這裡被繫結到類定義頭給出的類名稱(在上面的例子中就是ClassName
)。
類物件
類物件(比如上面例子的ClassName
)支援兩種操作:屬性引用和例項化。
屬性引用的語法跟Python中所有屬性引用的方法一樣:obj.name
。類物件被建立時存在於類名稱空間內的所有名稱都是有效的屬性名稱。下面是一個包含資料和方法的簡單的類定義:
class YuanRenXue:
"""A demo of class"""
name = '猿人學'
def say_hi(self):
print('Hello world!')
對這個類的有效的屬性引用就是:YuanRenXue.name
和YuanRenXue.say_hi
,它們分別返回一個字串和一個函式物件。
類屬性也可以被賦值,因此可以透過賦值來更改Yuanrenxue.name
的值。
類的__doc__
也是一個有效的屬性,對他的引用會返回所屬類的文件字串:'A demo of class'
。
類的例項化,是使用函式表示法,可以把類物件看做是會返回一個新的類例項的函式。比如上面類物件的例項化就是:
yrx = YuanRenXue()
這就建立了一個類的新例項並將詞物件分配給區域性變數yrx
。
例項化操作可以看成是“呼叫”類物件。但我們在建立類例項時都想要做些初始化操作,為此類定義時可以定義一個名為__init__()
的特殊方法。它是類例項化的初始化方法,跟C++語言中 的建構函式類似。
def __init__(self):
self.data = None
定義了__init__()
方法後,類的例項化操作會自動呼叫該方法。
當然,__init__()
方法也可以有額外(除self之外)的引數以實現更靈活的初始化操作。類物件例項化時(“呼叫”類物件)傳遞的引數會被傳遞給__init__()
方法。例如:
In [27]: class Point:
...: def __init__(self, x, y):
...: self.x = x
...: self.y = y
...:
In [28]: p = Point(7, 8)
In [29]: p.x, p.y
Out[29]: (7, 8)
例項物件
類例項化後我們就得到了例項物件,對它的操作就是:屬性引用。這裡的有效屬性名稱是資料屬性和方法。
o
資料屬性,對應C++中的“資料成員”,不懂C++也沒關係,“資料成員”這名字字面上就很形象。資料屬性不需要宣告,它像普通變數一樣,在第一次賦值時產生。比如p
是宣告建立的Point
的例項,則以下程式碼會列印數值8
:
p.times = 1
while p.times < 5:
p.times = p.times * 2
print(p.times)
del p.times
雖然p.times
並沒有在類定義時宣告(資料屬性不需要宣告),但在任何時候,我們可以給例項賦值一個新的資料屬性(這裡是p.times
),並可以隨時刪除例項的資料屬性(del p.times
)。
方法,是“從屬於”物件的函式。方法這個術語並不是類例項獨有的,其它物件也可以有方法。比如,列表物件有append(), insert(), sort()
等方法。
例項物件的有效方法名稱依賴於其所屬的類。 根據定義,一個類中所有是函式物件的屬性都是定義了其例項的相應方法。 因此在我們的示例中,yrx.say_hi
是有效的方法引用,因為YuanRenXue.say_hi
是一個函式;而yrx.name
不是方法,因為YuanRenXue.name
不是一個函式。這裡要注意,yrx.say_hi
與YuanRenXue.say_hi
並不是一回事,它是一個方法物件,不是函式物件,通俗講,前者是例項的方法,後者是類的函式。
方法物件
通常,呼叫方法的方法是:
yrx.say_hi()
在YuanRenXue 示例中,這將列印字串Hello World
。但是,呼叫一個方法也可以換另外一種形式,把它賦值給一個變數後再呼叫。例如:
yrx_say = yrx.say_hi
yrx_say()
當我們呼叫yrx.say_hi()
時並沒有帶引數,但say_hi()
函式定義時指定了一個引數。實際上,方法的特殊之處就是例項物件會作為函式的第一個引數(self)被傳入。呼叫yrx.say_hi()
其實就相當於YuanRenXue.say_hi(yrx)
。
類變數和例項變數
一般來說,類變數用於類的所有例項共享的屬性和方法,而例項變數用於每個例項的唯一資料:
In [33]: class Tiger:
...: kind = 'feline'
...: def __init__(self, name)
File "<ipython-input-33-16aa1a5937d1>", line 3
def __init__(self, name)
^
SyntaxError: invalid syntax
In [34]: class Tiger:
...: kind = 'feline'
...: def __init__(self, name):
...: self.name = name
...:
In [35]: a = Tiger('Kiro')
In [36]: b = Tiger('Zim')
In [37]: a.kind
Out[37]: 'feline'
In [38]: b.kind
Out[38]: 'feline'
In [39]: a.name
Out[39]: 'Kiro'
In [40]: b.name
Out[40]: 'Zim'
如果類變數是列表或字典這種可變物件會導致令人驚訝的結果,我們要明白這種結果,從而可以在需要的時候利用這個特性,也可以在不需要的時候避免這個特性。是取是舍,全在於我們在程式設計時要定義的類的作用。下面,我們看看這個“令人驚訝”的結果是什麼:
In [42]: class Tiger:
...: places = []
...: def __init__(self, name):
...: self.name = name
...: def go_place(self, place):
...: self.places.append(place)
...:
In [43]: a = Tiger('Kiro')
In [44]: b = Tiger('Zim')
In [45]: a.go_place('北京')
In [46]: b.go_place('上海')
In [47]: a.places
Out[47]: ['北京', '上海']
這裡,把places
定義為類變數,它就記錄了所有老虎(Kiro和Zim,以及後面例項化出來個各個老虎)去過的地方,所以,儘管a
只去過北京,但是當我們透過a
檢視places
,也看到了上海。這就是可變物件作為類變數時的特性。如果我們就是想記錄所有老虎例項物件去過的地方,這樣的用法就是恰到好處。
如果,我們只想記錄每一隻老虎去過的地方,那麼就應該把places
定義為例項變數,也就是在__init__()
中進行初始化賦值。
需要注意的地方
(1)資料屬性會覆蓋掉具有相同名稱的方法屬性,因此寫程式時為了避免名稱衝突,可以使用某些規範來減少衝突,比如:
- 方法名稱使用大寫字母;
- 屬性名稱加上特殊的字首(或加一個下劃線);
- 用動詞來命名方法,用名詞來命名資料屬性。
這些規範稱為“程式碼規範”,一個好的程式碼規範讓程式碼可讀性更高,也更利用團隊合作。有名的Python程式碼規範有“PEP 8”,也有Google的Python風格規範。
(2)資料屬性可以被方法以及物件之外的任何使用者(使用該類的人)所引用。也就是說,Python不行C++類那樣有私有成員,Python沒有任何物件能強制隱藏資料。
(3)類物件的使用者應該謹慎使用資料屬性,直接運算元據屬性可能破壞其中的固定變數。但我們可以想一個例項物件新增我們自己的資料屬性,但要避免與原有的名稱衝突。
(4)方法的第一個引數常常被命名為self
,代表例項物件。但這只是大家普通認同的一個約定。你可以用任何單詞來替代它,但是你的程式碼讓氣體程式設計師讀起來就很費勁,也會對VS Code這樣的編輯器造成困惑。
總結
類是組合資料和功能的方法,其中:
- 資料,是類的屬性,從屬於類的各種資料物件;
- 功能,是類的方法,定義在類內部的各種函式。
定義好一個類我們就建立了一個類物件,類物件支援兩種操作:屬性引用和例項化。
類物件例項化後就得到了例項物件,它有兩種屬性名稱:資料屬性和方法。
類變數是所有類的例項共享的屬性和方法,例項變數是每個例項獨有的資料。
我的公眾號:猿人學 Python 上會分享更多心得體會,敬請關注。
***版權申明:若沒有特殊說明,文章皆是猿人學 yuanrenxue.com 原創,沒有猿人學授權,請勿以任何形式轉載。***