Python學習入門(27)—類(之二)

(本號正在連續推出以Python官網文檔為主線的完整的系統學習Python的系列文章或視頻,感興趣的朋友們歡迎搜索關注。在這裡學習Python事半功倍!本文及後續文章如無特別聲明均以Windows平臺作為演示平臺,Python版本為:3.8.1)


【本篇繼上篇 繼續講解關於"類"的內容】

類的定義語法

最簡單的類的定義形式如下:

<code>class ClassName:
<statement-1>
.
.
.
<statement-n>/<statement-1>/<code>

正如函數定義(def語句)一樣,類在使用之前必須定義,並且定義必須已經生效(可以將類定義放在if語句的分支中,或者放在函數中。)

在實際中,類定義除了包含用來定義函數的語句外,還允許使用其他語句。類內的函數定義通常有一個特殊形式的參數列表,由方法的調用約定決定,稍後將對此進行解釋。

當執行一個類定義時,會創建一個新的命名空間,並將其用作局部作用域。所有對局部變量的賦值都會進入到這個新的命名空間,類函數的定義也將函數的名稱綁定在這裡。

在程序執行過程中,當一個類定義被正常執行完畢(通過末尾)後,一個類對象就被創建了。類對象基本上是對類定義創建的命名空間內容的包裝。隨後將恢復原來的局部作用域(在進入入類定義之前生效的作用域),類對象將其類名綁定在這裡。

類對象

類對象支持兩種操作:屬性引用和實例化。

屬性引用使用Python中所有屬性引用使用的標準語法:obj.name。有效屬性名是類對象創建時類命名空間中的所有名稱。所以,如果類定義是這樣的:

<code>class MyClass:
"""A simple example class"""
i = 12345

def f(self):
return 'hello world'/<code>

那麼MyClass.i和MyClass.f是有效的屬性引用,分別返回一個整數和一個函數對象。類屬性也可以被賦值,因此定義後還可以修改MyClass.i的值。__doc__同樣是一個有效的屬性,用於返回該類的文檔字符串:“A simple example class”。

類實例化使用函數表示法。可以假定認為類對象是一個無參數的函數,對它的調用將返回類的一個新實例。例如:

<code>x = MyClass()/<code>

創建了類的新實例並將此對象分配給局部變量x。實例化操作(“調用”類對象)創建一個空對象。有時候需要創建具有特定初始狀態的實例對象,為此一個類需要定義一個名為_init__()的特殊方法,如下所示:

<code>def __init__(self):
self.data = []/<code>

當一個類定義了一個_init__()方法時,類實例化會自動為新創建的實例調用_init__()。所以在這個例子中,一個新的初始化的實例可以通過如下獲得:

<code>x = MyClass()/<code>

一般來說用_init__()方法可能是為了更靈活地實例化類,此時需要在類實例化時傳遞參數,該參數將被傳遞給__init__()。例如:

<code>>>> class Complex:
... def __init__(self, realpart, imagpart):
... self.r = realpart
... self.i = imagpart
...
>>> x = Complex(3.0, -4.5)
>>> x.r, x.i
(3.0, -4.5)/<code>

實例對象

實例對象可以做什麼?實例對象能理解的唯一操作就是屬性引用。有兩種有效的屬性名:數據屬性和方法。數據屬性在Smalltalk中對應“實例變量”,在C++中對應“數據成員”。數據屬性是不需要聲明的,像局部變量一樣它們在第一次被賦值時就出現了。例如,如果x是上面創建的MyClass的實例,下面的代碼將打印值16,而不留下任何痕跡:

<code>x.counter = 1 

while x.counter < 10:
x.counter = x.counter * 2
print(x.counter)
del x.counter/<code>

另一種實例屬性引用是方法。方法是“屬於”對象的函數。(在Python中,方法這個術語並不是類實例所特有的,其他對象類型也可以有方法。例如,list對象有append、insert、remove、sort等方法。但是,在接下來的討論中,除非另有明確說明外,我們將專門使用方法這個術語來表示類實例對象的方法。)

實例對象的方法名是否有效取決於具有該名稱的方法是否在實例的類中已經定義。類的函數對象屬性定義了其實例的相應方法。在我們的例子中,x.f是一個有效的方法引用,因為MyClass.f是一個函數,但是x.i不是,因為MyClass.i不是函數對象。注意:x.f與MyClass.f是不同的,前者是方法對象,後者是函數對象。

方法對象

通常一個方法綁定後就可以調用,如x.f()。在MyClass示例中,它將返回字符串“hello world”。也可以將一個方法對象存儲起來在以後調用。例如下例將一直打印hello world直到用戶中斷為止:

<code>xf = x.f
while True:
print(xf())/<code>

現在考慮一下,當一個方法被調用時發生了什麼?可能已經注意到,雖然f()的函數定義中指定了一個參數,但是調用x.f()時沒有使用上面的參數。要知道,當需要參數的函數被調用時如果未指定相應的參數時,Python會拋出一個異常,即使這個參數實際上沒有被使用也是如此。實際上,此處也是方法的特殊之處:調用方法時將轉換為調用對應的類函數,實例對象將作為的第一個參數傳遞,這個過程是透明的,也就是用戶在調用方法時只需要傳遞除第一個參數外的其它參數即可。在我們的例子中,調用x.f()與MyClass.f(x)完全等價。通常,用n個參數的列表調用方法等價於用在該參數列表前添加其實例對象後作為參數列表來調用相應的類函數。

如果您仍然不理解方法是如何工作的,那就看下它是怎麼實現的,這樣會進一步幫助理解。當引用實例的非數據屬性時,將搜索實例的類。如果該名稱表示一個有效的類屬性並且是一個函數對象,那麼通過將實例對象和函數對象(的指針)打包在一個抽象對象中來創建一個方法對象。當使用參數列表調用方法對象時,將從實例對象和參數列表構造一個新的參數列表,並使用這個新的參數列表調用對應的類函數。

類變量和實例變量

一般來說,實例變量是每個實例唯一的數據,類變量是類的所有實例共享的屬性和方法:

<code>class Dog:

kind = 'canine' # class variable shared by all instances

def __init__(self, name):
self.name = name # instance variable unique to each instance

>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.kind # shared by all dogs
'canine'
>>> e.kind # shared by all dogs
'canine'
>>> d.name # unique to d
'Fido'
>>> e.name # unique to e
'Buddy'/<code>

前面已經提到當過,共享數據涉及到列表和字典等可變對象時可能會產生意外的結果。例如,下面代碼中的tricks不應該用作類變量,因為作為類變量的列表會被所有Dog實例共享:

<code>class Dog:

tricks = [] # mistaken use of a class variable

def __init__(self, name):
self.name = name

def add_trick(self, trick):
self.tricks.append(trick)

>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')

>>> e.add_trick('play dead')
>>> d.tricks # unexpectedly shared by all dogs
['roll over', 'play dead']/<code>

正確的類設計應該使用一個實例變量:

<code>class Dog:

def __init__(self, name):
self.name = name
self.tricks = [] # creates a new empty list for each dog

def add_trick(self, trick):
self.tricks.append(trick)

>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks
['roll over']
>>> e.tricks
['play dead']/<code>


[關於"類"的內容本篇未完,下篇將繼續講解]

【結束】


分享到:


相關文章: