Python 入門教程/類
在程式設計中,你將會了解到程式設計師喜歡偷懶。如果有些東西已經做過,為什麼要再做一遍呢?
這就是 Python 中函式所涵蓋的。你的程式碼已經做過一些特別的事情了,現在你想再做一遍。你把那段特殊的程式碼放進一個函式,並儘可能地重複使用它。你可以在程式碼中的任何地方引用一個函式,計算機始終會知道你在說什麼。方便吧?
當然,函式也有其侷限性。函式不像變數那樣儲存任何資訊 - 每次執行函式時,它都會從頭開始。但是,某些函式和變數彼此關係非常密切,並且需要經常相互作用。例如,想象一下你有一根高爾夫球杆。它包含有關它的資訊(即變數),例如杆身的長度、握柄的材質和球頭的材質。它還與之相關的函式,例如揮動高爾夫球杆的函式或因純粹的沮喪而將其打碎的函式。對於這些函式,你需要知道杆身長度、球頭材質等的變數。
使用普通的函式可以輕鬆地解決這個問題。引數會影響函式的效果。但是,如果一個函式需要影響變數呢?如果每次使用你的高爾夫球杆時,杆身都會變得更弱,握柄上的手柄會磨損一點,你會變得更加沮喪,並且在球頭上會形成新的劃痕?一個函式無法做到這一點。一個函式只能生成一個輸出,而不是四個或五個,或五百個。我們需要一種方法將密切相關的函式和變數分組到一個地方,以便它們可以相互作用。
你可能不止一根高爾夫球杆。如果沒有類,你需要為每根不同的高爾夫球杆編寫一大堆程式碼。這很痛苦,因為所有球杆都共享共同的特徵,只是有些屬性有所改變 - 例如杆身由什麼製成以及它的重量。理想的情況是,有一個基本高爾夫球杆的設計。每次建立一個新球杆時,只需指定其屬性 - 杆身的長度、重量等。
或者,如果你想要一根具有額外功能的高爾夫球杆呢?也許你決定在你的高爾夫球杆上安裝一個時鐘(為什麼,我不知道 - 這是你的主意)。這是否意味著我們必須從頭開始建立這根高爾夫球杆?我們必須首先為基本高爾夫球杆編寫程式碼,然後再編寫所有這些程式碼,以及時鐘的程式碼,用於我們的新設計。如果我們只是拿我們現有的高爾夫球杆,然後將時鐘的程式碼附加到它上面,會不會更好?
這些問題是面向物件程式設計解決的。它以一種能夠相互看到並一起工作、根據需要複製和修改的方式將函式和變數組合在一起,而不是在不需要的時候這樣做。我們使用名為“類”的東西來實現這一點。
什麼是類?將類想象成一個藍圖。它本身並不是什麼東西,它只是描述瞭如何製造東西。你可以從這個藍圖建立很多物件 - 在技術上稱為例項。
那麼如何建立這些所謂的“類”呢?非常簡單,使用class運算子
- 程式碼示例 1 - 定義一個類
# Defining a class
class class_name:
[statement 1]
[statement 2]
[statement 3]
[etc.]
不太理解?沒關係,這裡有一個例子建立了 Shape 的定義
- 程式碼示例 2 - 類的示例
#An example of a class
class Shape:
def __init__(self, x, y):
self.x = x
self.y = y
self.description = "This shape has not been described yet"
self.author = "Nobody has claimed to make this shape yet"
def area(self):
return self.x * self.y
def perimeter(self):
return 2 * self.x + 2 * self.y
def describe(self, text):
self.description = text
def authorName(self, text):
self.author = text
def scaleSize(self, scale):
self.x = self.x * scale
self.y = self.y * scale
你建立的是對形狀的描述(即變數)以及你可以對形狀進行的操作(即函式)。這非常重要 - 你並沒有建立實際的形狀,而只是建立了形狀的描述。該形狀具有寬度 (x)、高度 (y) 以及面積和周長 (area(self) 和 perimeter(self))。當你定義一個類時,不會執行任何程式碼 - 你只是在建立函式和變數。
名為 __init__ 的函式在建立 Shape 的例項時執行 - 也就是說,當我們建立實際的形狀時,而不是我們這裡的“藍圖”,__init__ 會執行。你稍後會明白它是如何工作的。
self 是我們在類內部從類內部引用事物的方式。self 是在類內部定義的任何函式的第一個引數。在第一個縮排級別(即程式碼行,這些程式碼行從我們放置 class Shape 的位置向右縮排一個 TAB)上建立的任何函式或變數都會自動放入 self。要在類內部的別處訪問這些函式和變數,它們的名稱必須以 self 和一個句點開頭(例如 self.variable_name)。
我們可以建立類,但如何使用它呢?這裡是一個建立“類的例項”的示例。假設示例 2 中的程式碼已經執行
- 程式碼示例 3 - 建立類
rectangle = Shape(100, 45)
做了什麼?這需要一些解釋...
__init__ 函式在此時真正發揮作用。我們透過首先給出它的名稱(在本例中為 Shape)來建立一個類的例項,然後在方括號中,給出要傳遞給 __init__ 函式的值。init 函式執行(使用你在方括號中給出的引數),然後輸出該類的例項,在本例中分配給名稱“rectangle”。
將我們的類例項 rectangle 想象成一個自包含的變數和函式集合。就像我們在類內部從類內部使用 self 來訪問類例項的函式和變數一樣,我們現在使用分配給它的名稱(rectangle)來從類外部訪問類例項的函式和變數。接著我們上面執行的程式碼,我們這樣做
- 程式碼示例 4 - 從例項外部訪問屬性
#finding the area of your rectangle:
print(rectangle.area())
#finding the perimeter of your rectangle:
print(rectangle.perimeter())
#describing the rectangle
rectangle.describe("A wide rectangle, more than twice as wide as it is tall")
#making the rectangle 50% smaller
rectangle.scaleSize(0.5)
#re-printing the new area of the rectangle
print(rectangle.area())
如你所見,在類例項內部使用 self 的地方,在類外部使用其分配的名稱。我們這樣做是為了檢視和更改類內部的變數,以及訪問那裡的函式。
我們並不侷限於一個類的單個例項 - 我們可以建立任意數量的例項。我可以這樣做
- 程式碼示例 5 - 多個例項
long_rectangle = Shape(120,10)
fat_rectangle = Shape(130,120)
long_rectangle 和 fat_rectangle 都有自己的函式和變數包含在它們內部 - 它們彼此完全獨立。我可以建立的例項數量沒有限制。
面向物件程式設計有一套與之相關的術語。是時候把這些都弄清楚了
- 當我們第一次描述一個類時,我們是在定義它(就像函式一樣)
- 將類似的函式和變數分組在一起的能力稱為封裝
- 單詞“類”可以用來描述定義類的程式碼(就像定義函式一樣),也可以用來指代該類的例項 - 這可能會讓人困惑,所以請確保你知道我們指的是哪種形式的類
- 類內部的變數稱為“屬性”
- 類內部的函式稱為“方法”
- 類與變數、列表、字典等屬於同一類事物。也就是說,它們是物件
- 類被稱為“資料結構” - 它儲存資料以及處理這些資料的函式。
讓我們回顧一下引言。我們知道類如何將變數和函式(稱為屬性和方法)分組在一起,以便資料和處理資料的程式碼都在同一個位置。我們可以建立該類的任意數量的例項,這樣我們就不必為每個新建立的物件編寫新的程式碼。但是如何向我們的高爾夫球杆設計中新增額外功能呢?這就是繼承發揮作用的地方。
Python 使繼承非常容易。我們基於另一個“父”類定義一個新類。我們的新類從父類中繼承了一切,我們還可以新增其他東西。如果任何新的屬性或方法與父類中的屬性或方法同名,則使用它而不是父類中的那個。還記得 Shape 類嗎?
- 程式碼示例 6 - Shape 類
class Shape:
def __init__(self,x,y):
self.x = x
self.y = y
self.description = "This shape has not been described yet"
self.author = "Nobody has claimed to make this shape yet"
def area(self):
return self.x * self.y
def perimeter(self):
return 2 * self.x + 2 * self.y
def describe(self,text):
self.description = text
def authorName(self,text):
self.author = text
def scaleSize(self,scale):
self.x = self.x * scale
self.y = self.y * scale
如果我們想定義一個新的類,比如一個正方形,基於我們之前的 Shape 類,我們會這樣做
- 程式碼示例 7 - 使用繼承
class Square(Shape):
def __init__(self,x):
self.x = x
self.y = x
這就像通常定義一個類一樣,但這次我們在類名後面用方括號括起要繼承的父類。如你所見,這讓我們可以很快地描述一個正方形。這是因為我們繼承了形狀類的所有內容,並且只改變了需要改變的部分。在這種情況下,我們重新定義了 Shape 的__init__ 函式,以便 X 和 Y 值相同。
讓我們從所學到的知識中汲取經驗,建立一個新的類,這次繼承自 Square。它將是兩個正方形,一個緊挨著另一個的左側。
- 程式碼示例 8 - DoubleSquare 類
# The shape looks like this:
# _________
#| | |
#|____|____|
class DoubleSquare(Square):
def __init__(self,y):
self.x = 2 * y
self.y = y
def perimeter(self):
return 2 * self.x + 3 * self.y
這一次,我們還必須重新定義 perimeter 函式,因為形狀中間有一條線。嘗試建立一個此類的例項。作為提示,IDLE 命令列從你的程式碼結束的地方開始 - 所以輸入一行程式碼就像在你的程式末尾新增該行。
回想一下,當你宣告一個變數等於另一個變數時,例如 variable2 = variable1,等號左側的變數將採用等號右側變數的值。對於類例項,這在某種程度上有所不同 - 左側的名稱將成為右側的類例項。因此在 instance2 = instance1 中,instance2 指向 instance1 - 兩個名稱都指向同一個類例項,你可以透過任一名稱訪問該類例項。
在其他語言中,你使用指標執行類似的操作,但在 Python 中,所有這些都在幕後完成。
我們將要介紹的最後一件事是類字典。記住我們剛剛學到的關於指標的內容,我們可以將一個類的例項分配給列表或字典中的一個條目。這使得我們的程式執行時,幾乎可以建立任意數量的類例項。讓我們看下面的例子,並看看它是如何描述我的意思的。
- 程式碼示例 9 - 類字典
# Again, assume the definitions on Shape,
# Square and DoubleSquare have been run.
# First, create a dictionary:
dictionary = {}
# Then, create some instances of classes in the dictionary:
dictionary["DoubleSquare 1"] = DoubleSquare(5)
dictionary["long rectangle"] = Shape(600,45)
#You can now use them like a normal class:
print(dictionary["long rectangle"].area())
dictionary["DoubleSquare 1"].authorName("The Gingerbread Man")
print(dictionary["DoubleSquare 1"].author)
如你所見,我們只是用一個激動人心的、新的、動態的字典條目替換了我們無聊的舊左側名稱。很酷吧?