跳轉到內容

Think Python/類和物件

來自 Wikibooks,開放世界中的開放書籍

使用者定義型別

[edit | edit source]

我們已經使用了 Python 的許多內建型別;現在我們將定義一個新的型別。舉個例子,我們將建立一個名為Point的型別,它代表二維空間中的一個點。

在數學符號中,點通常用括號括起來,用逗號分隔座標。例如,(0, 0) 代表原點,(x, y) 代表從原點向右移動 x 個單位,向上移動 y 個單位的點。

在 Python 中,我們可以用幾種方式表示點

  • 我們可以將座標分別儲存在兩個變數中:xy.
  • 我們可以將座標儲存為列表或元組中的元素。
  • 我們可以建立一個新型別來表示點作為物件。

建立新型別比其他選項(稍微)更復雜,但它有一些很快就會顯現的優勢。

使用者定義型別也稱為。類定義看起來像這樣

class Point(object):
    """represents a point in 2-D space"""

此標題表示新類是一種Point,它是object的一種,而它是內建型別。

主體是一個文件字串,它解釋了類的用途。你可以在類定義中定義變數和函式,但我們稍後會回到這一點。

定義名為Point的類會建立一個類物件。

>>> print Point
<class '__main__.Point'>

因為Point是在頂層定義的,所以它的“全名”是 __main__.Point

類物件就像一個用於建立物件的工廠。要建立一個 Point,你需要呼叫Point,就像它是一個函式一樣。

>>> blank = Point()
>>> print blank
<__main__.Point instance at 0xb7e9d3ac>

返回值是對 Point 物件的引用,我們將它分配給空白。建立新物件稱為例項化,而該物件是類的例項

當你列印一個例項時,Python 會告訴你它屬於哪個類,以及它儲存在記憶體中的位置(字首0x表示後面的數字是十六進位制的)。

屬性

[edit | edit source]

你可以使用點表示法為例項分配值

>>> blank.x = 3.0
>>> blank.y = 4.0

此語法類似於從模組中選擇變數的語法,例如math.pistring.whitespace。但是,在這種情況下,我們正在將值分配給物件的命名元素。這些元素稱為屬性

作為名詞,“AT-trib-ute”的重音在第一個音節上,而“a-TRIB-ute”是動詞。

下圖顯示了這些賦值的結果。顯示物件及其屬性的狀態圖稱為物件圖

File:Book022.png

變數空白引用一個 Point 物件,該物件包含兩個屬性。每個屬性都引用一個浮點數。

你可以使用相同的語法讀取屬性的值

>>> print blank.y
4.0
>>> x = blank.x
>>> print x
3.0

表示式blank.x的意思是,“轉到物件空白所引用的位置,並獲取x的值”。在這種情況下,我們將該值分配給一個名為x的變數。變數x和屬性x.

之間不存在衝突。你可以在任何表示式中使用點表示法。例如

>>> print '(%g, %g)' % (blank.x, blank.y)
(3.0, 4.0)
>>> distance = math.sqrt(blank.x**2 + blank.y**2)
>>> print distance
5.0

你可以按常規方式將例項作為引數傳遞。例如

def print_point(p):
    print '(%g, %g)' % (p.x, p.y)

print_point 接受一個點作為引數,並以數學符號顯示它。要呼叫它,你可以傳遞空白作為引數

>>> print_point(blank)
(3.0, 4.0)

在函式內部,p空白的別名,因此如果函式修改了p, 空白,則會發生改變。

練習 1

[edit | edit source]

編寫一個名為“distance”的函式,它接受兩個 Point 作為引數,並返回它們之間的距離。

矩形

[edit | edit source]

有時,物件的屬性是顯而易見的,但有時你需要做出決定。例如,想象你正在設計一個類來表示矩形。你會使用哪些屬性來指定矩形的位置和大小?你可以忽略角度;為了簡化起見,假設矩形是垂直或水平的。

至少有兩種可能性

  • 你可以指定矩形的一個角(或中心)、寬度和高度。
  • 你可以指定兩個相對的角。

在這一點上,很難說哪一個比另一個更好,所以我們將實現第一個,僅僅作為示例。

這是類定義

class Rectangle(object):
    """represent a rectangle. 
       attributes: width, height, corner.
    """

文件字串列出了屬性widthheight是數字;corner是一個 Point 物件,它指定了左下角。

要表示一個矩形,你需要例項化一個 Rectangle 物件,併為屬性分配值

box = Rectangle()
box.width = 100.0
box.height = 200.0
box.corner = Point()
box.corner.x = 0.0
box.corner.y = 0.0

表示式box.corner.x的意思是,“轉到物件box所引用的位置,並選擇名為corner的屬性;然後轉到該物件,並選擇名為x的屬性。”

該圖顯示了該物件的狀態

File:Book023.png 作為另一個物件屬性的物件是嵌入的。

例項作為返回值

[edit | edit source]

函式可以返回例項。例如,find_center 接受一個Rectangle作為引數,並返回一個Point,其中包含Rectangle:

def find_center(box):
    p = Point()
    p.x = box.corner.x + box.width/2.0
    p.y = box.corner.y + box.height/2.0
    return p

中心的座標。以下是一個將box作為引數傳遞並將生成的 Point 分配給center:

>>> center = find_center(box)
>>> print_point(center)
(50.0, 100.0)

物件的例子。

物件是可變的

[edit | edit source]widthheight:

box.width = box.width + 50
box.height = box.height + 100

你可以透過對物件的某個屬性進行賦值來改變物件的狀態。例如,要改變矩形的大小而不改變其位置,你可以修改的值。你也可以編寫修改物件的函式。例如,grow_rectangle 接受一個 Rectangle 物件和兩個數字,dwidthdheight

def grow_rectangle(rect, dwidth, dheight) :
    rect.width += dwidth
    rect.height += dheight

,並將這些數字新增到矩形的寬度和高度

>>> print box.width
100.0
>>> print box.height
200.0
>>> grow_rectangle(box, 50, 100)
>>> print box.width
150.0
>>> print box.height
300.0

在函式內部,以下是一個演示效果的例子box的別名,因此如果函式修改了以下是一個演示效果的例子, box,則會發生改變。

rect

練習 2

[edit | edit source]編寫一個名為 move_rectangle 的函式,它接受一個 Rectangle 和兩個名為dxdy編寫一個名為 move_rectangle 的函式,它接受一個 Rectangle 和兩個名為的數字。它應該透過將x新增到corner的座標和將dx的數字。它應該透過將y新增到corner.

新增到

的座標來改變矩形的位置。

複製

[edit | edit source]別名會使程式難以閱讀,因為在一個地方的更改可能會在另一個地方產生意想不到的影響。很難跟蹤所有可能引用給定物件的變數。複製物件通常是別名的替代方案。該別名會使程式難以閱讀,因為在一個地方的更改可能會在另一個地方產生意想不到的影響。很難跟蹤所有可能引用給定物件的變數。copy

>>> p1 = Point()
>>> p1.x = 3.0
>>> p1.y = 4.0

>>> import copy
>>> p2 = copy.copy(p1)

模組包含一個名為的函式,它可以複製任何物件p1

>>> print_point(p1)
(3.0, 4.0)
>>> print_point(p2)
(3.0, 4.0)
>>> p1 is p2
False
>>> p1 == p2
False

p2包含相同的資料,但它們不是同一個 Point。模組包含一個名為的函式,它可以複製任何物件運算子表示==不是同一個物件,這正如我們所料。但你可能期望產生True==,因為這些點包含相同的資料。在這種情況下,你會很失望地發現,對於例項,該包含相同的資料,但它們不是同一個 Point。運算子的預設行為與該

運算子相同;它檢查物件標識,而不是物件等效性。此行為可以更改——我們稍後會看到如何更改。如果你使用要複製一個矩形,你會發現它複製了矩形物件,但沒有複製嵌入的點物件。

>>> box2 = copy.copy(box)
>>> box2 is box
False
>>> box2.corner is box.corner
True

以下是物件圖的樣子:

<IMG SRC="book024.png">

此操作稱為淺複製,因為它複製了物件及其包含的任何引用,但不復制嵌入的物件。

對於大多數應用程式來說,這不是你想要的。在這個例子中,對其中一個矩形呼叫grow_rectangle不會影響另一個矩形,但是對任何一個矩形呼叫move_rectangle都會影響兩個矩形!這種行為令人困惑且容易出錯。

幸運的是,別名會使程式難以閱讀,因為在一個地方的更改可能會在另一個地方產生意想不到的影響。很難跟蹤所有可能引用給定物件的變數。模組包含一個名為deepcopy的方法,它不僅複製了物件,還複製了它引用的物件,以及它們引用的物件,等等。你不會驚訝地發現這個操作被稱為深複製

>>> box3 = copy.deepcopy(box)
>>> box3 is box
False
>>> box3.corner is box.corner
False

box3box是完全獨立的物件。

編寫一個move_rectangle版本,它建立並返回一個新的矩形,而不是修改舊的矩形。

當你開始使用物件時,你可能會遇到一些新的異常。如果你嘗試訪問不存在的屬性,你會得到一個AttributeError:

>>> p = Point()
>>> print p.z
AttributeError: Point instance has no attribute 'z'

如果你不確定物件的型別,你可以詢問

>>> type(p)
&lt;type '__main__.Point'>

如果你不確定物件是否具有特定屬性,可以使用內建函式hasattr:

>>> hasattr(p, 'x')
True
>>> hasattr(p, 'z')
False

第一個引數可以是任何物件;第二個引數是一個字串,包含屬性的名稱。

詞彙表

[編輯 | 編輯原始碼]
class
使用者定義的型別。類定義建立新的類物件。
類物件
包含使用者定義型別資訊的物件。類物件可以用來建立該型別的例項。
例項
屬於某個類的物件。
屬性
與物件關聯的命名值之一。
嵌入(物件)
作為另一個物件屬性儲存的物件。
淺複製
複製物件的內容,包括對嵌入物件的任何引用;由別名會使程式難以閱讀,因為在一個地方的更改可能會在另一個地方產生意想不到的影響。很難跟蹤所有可能引用給定物件的變數。函式在別名會使程式難以閱讀,因為在一個地方的更改可能會在另一個地方產生意想不到的影響。很難跟蹤所有可能引用給定物件的變數。模組中實現。
深複製
複製物件的內容以及任何嵌入的物件,以及它們嵌入的任何物件,等等;由deepcopy函式在別名會使程式難以閱讀,因為在一個地方的更改可能會在另一個地方產生意想不到的影響。很難跟蹤所有可能引用給定物件的變數。模組中實現。
物件圖
一個圖,它顯示了物件、它們的屬性和屬性的值。

World.py,它是 Swampy 的一部分(見第 '4' 章),包含一個用於稱為 World 的使用者定義型別的類定義。如果你執行這段程式碼: from World import * world = World() wait_for_user() 應該會彈出一個帶標題欄和一個空正方形的視窗。在本練習中,我們將使用此視窗繪製點、矩形和其他形狀。在wait_for_user之前新增以下幾行,然後再次執行程式

''canvas = world.ca(width=500, height=500, background='white')
bbox = [[-150,-100], [150, 100]]
canvas.rectangle(bbox, outline='black', width=2, fill='green4')

你應該看到一個帶有黑色邊框的綠色矩形。第一行建立了一個 Canvas,它在視窗中顯示為一個白色正方形。Canvas 物件提供了像rectangle這樣的方法,用於繪製各種形狀。

bbox是一個列表的列表,它表示矩形的“邊界框”。第一對座標是矩形的左下角;第二對座標是右上角。

你可以這樣繪製一個圓形

canvas.circle([-25,0], 70, outline=None, fill='red')

第一個引數是圓心座標對;第二個引數是半徑。

如果你在程式中新增這行程式碼,結果應該類似於孟加拉國國旗(見wikipedia.org/wiki/Gallery_of_sovereign-state_flags).

  • 編寫一個名為draw_rectangle的函式,它接收一個 Canvas 和一個矩形作為引數,並在 Canvas 上繪製矩形的表示。
  • 在你的矩形物件中新增一個名為color的屬性,並修改draw_rectangle,以便它使用 color 屬性作為填充顏色。
  • 編寫一個名為draw_point的函式,它接收一個 Canvas 和一個點作為引數,並在 Canvas 上繪製點的表示。
  • 定義一個名為 Circle 的新類,它具有適當的屬性,並例項化幾個 Circle 物件。編寫一個名為draw_circle的函式,它在畫布上繪製圓形。
points = [[-150,-100], [150, 100], [150, -100]]
canvas.polygon(points, fill='blue')

我寫了一個小程式,它列出了可用的顏色;你可以從thinkpython.com/code/color_list.py.

華夏公益教科書