第六章、喔喔:物件與類別
- 什麼是物件?
Python內的任何一個東西都是一種物件,但是Python使用特殊的語法來將大部分的物件機制隱藏起來。
物件裡面有資料(變數,稱為 屬性 ),有程式碼(函式,稱為 方法 ),它代表某種具體事物唯一的實例。
當你要建立一個沒有人提過的新物件時,必須先建立一個類別來說明他們擁有些什麼。
與模組不同的是,你可以同時擁有多個物件,每一個物件的屬性與有不同的值,他們就像被丟入程式碼的超級資料結構。
- 用class來定義類別
要在Python中建立你的自訂物件,你必須先使用class關鍵字來定義一個類別。
※__init__是特殊的python名稱,代表一個方法,會用他的類別定義來初始化一個單一物件
並非所有的類別定義式都要用到它,它的用途是為了協助分辨同一個類別製造出來的物件
※在類別定義式中定義__init__()時,它的第一個參數必須式self,這個self則是一種慣例
以後別人閱讀你的程式時就不需要猜測你的意思了
- 自訂第一個類別
※當在類別定義式中定義__init__時,第一個參數必須是self
※在初始化方法中添加一個參數 name
※以下是上述程式所做的事情;
- 查看Person類別的定義
- 在記憶體中實例化(建立)一個新物件
- 呼叫該物件中的__init__方法,傳遞這個新物件給self,以及另一個引數('Ivanpotato')給name
- 在物件中儲存name的值
- 回傳新物件
- 將名稱hunter指派給新物件
※這個新物件就像任額其他的python新物件。可以將它當成串列、tuple、字典或集合。
你也可以將它當成引數傳給函式,或將它當成結果回傳。
※在Person類別定義式裡面,你可以用self.name來存取name屬性。
而當你建立一個實際物件,例如hunter時,要用hunter.name來參考它
- 繼承
當你試著解決一些程式問題的時候,通常會發現有一個既有類別所建立的物件已經可以大致滿足你的需求了。
這時就可以使用 繼承 :使用既有的類別來建立一個新類別,加入一些新的東西,或是修改它。
※原本的類別稱為 父系(parent)、超類別(superclass)、或基礎類別(base class)
新類別稱為 子系(child)、子類別(subclass)、或衍生類別(derived class)
※新類別可以使用舊類別的所有程式,但不需要複製任何舊程式
※你可以只在新類別定義想要添加或改變的東西,他們會改變舊類別的行為
- 自訂一個繼承
※Yugo 會從 Car 繼承 exclaim 方法。 接下來我們來看如何覆寫。
- 覆寫方法
※在上述範例中,覆寫了exclaim()的方法
- 添加方法
你也可以在子類別加入父類別沒有的方法
※此時,Yugo 可以做些 Car 無法辦到的事情,你可以清楚的看到 Yugo 的差異
- 用 super 來讓父系幫助你
之前已經看過子類別如何添加或覆寫父類別的方法。如果想要呼叫父類別的方法時,可以使用 super()
- self防衛
實例方法的第一個引數必須使用 self,Python 會使用self引數來尋找正確物件的屬性與方法
- 使用特性來取得屬性值與設定它
有些物件導向語言會提供私有物件屬性,你無法外不存取它們。Python則是使用 特性property
※新方法的行為就像其他語言的getter跟setter,它將這兩個方法定義成name屬性的特性
第一個傳入property()的引數是getter方法,第二個是setter。
※當你參考任何Duck物件的name時,它其實會呼叫get_name()方法,並回傳它
※你還是可以直接呼叫get_name(),就像一般的getter一樣
※當你將一個值只派給name屬性時,就會呼叫set_name()方法
※你還是可以直接呼叫set_name()方法
- 另一個定義特性的方法則是使用裝飾器
- @property 在getter 方法之前
- @name.setter 在setter方法之前
※還是可以將name當成屬性來存取,但這裡看不到get_name()或set_name()方法
- 特性也可以參考算出來的值
下面的例子,定義一個circle,它有一個raduis屬性,與一個算出來的diameter屬性
- 方法類型
類別方法會影響整個類別,你對類別鎖做的任何修改,都會影響它整個物件
※是使用A.count(類別屬性)而不是使用self.count(物件實例屬性)
在kids()方法中,可使用cls.count也可以使用A.count
- Duck Typing
可以將同樣的動作應用在不同的物件上,無論它們的類別是什麼。
- 以下會在三個Quote類別使用相同的__init__初始程式,但加入兩個新函式:
- who() 只會回傳被存起來的person字串的值
- says()會回傳被儲存的字串,並使用特定的標點符號
※三種不同的says()版本提供不同的行為給三個類別。這是傳統物件導向的語言多型
※跟之前的Quote完全無關的類別,但同樣有who()跟says()函式
※這時建立一個新函式,可以看到,引數設為類別的話,則根據不同的引數會給出不同類別的結果
這個行為稱做 duck typing
- 特殊方法
這些方法都會使用雙底線( __ ),下面會舉出各種運算子,也可以稱呼這些方法為"魔術方法"。
※上面的是我們自己定義的equal()方法,更好的做法則是使用Python的內建風格--使用特殊方法
※這裡則是使用特殊方法中的 __eq__(),可以發現能直接使用 == 來運算
- 比較魔術方法
- ------------------------------------
__eq__(self, other) self == other
__ne__(self, other) self != other
__lt__(self, other) self < other
__gt__(self, other) self > other
__le__(self, other) self <= other
__ge__(self, other) self >= other
- ------------------------------------
- 數學魔術方法
- ------------------------------------
__add__(self, other) self + other
__sub__(self, other) self - other
__mul__(self, other) self * other
__floordiv__(self, other) self // other(取商,無條件捨去)
__truediv__(self, other) self / other
__mod__(self, other) self % other
__pow__(self, other) self ** other (次方)
- ------------------------------------
- 其他魔術方法
- ------------------------------------
__str__(self, other) str(self)
__repr__(self, other) repr(self)
__len__(self, other) len(self)
- ------------------------------------
※以上將__str_() 與 __repr__() 方法加入到類別,來讓它更完美
- 組合
當你希望子類別的動作大多與他的父類別相同時,可以打造精緻的繼承層次,但有時候也可以使用"組合"或是"聚合"
- 使用 類別與物件 v.s. 模組 的時機
- 使用物件>需要許多實例,且它們許多類似行為(方法),但內部狀態不同(屬性)
- 使用模組>如果某個東西只需要一個
- 使用類別>有許多變數,裡面有許多值,值又可以當成引數傳給函式
- 類別可繼承,模組不行
- 盡可能用簡單的方式完成(字典串列、tuple都比模組簡單,模組通常比類別簡單)
- 具名Tuple
具名 tuple 是一種 tuple 的子類別,可以用名稱(使用 .name)以及位置(使用 [offset]) 來存取值
具名tuple的優點:
- 它的外觀與行為都像個不可變物件
- 它比物件節省空間與時間
- 可以使用句點標記法取代字典型式的方括號來存取屬性
- 你可以將它當成字典鍵來使用
※在使用namedtuple會使用兩個引數:
名稱、欄位名稱字串(以空格分開)
- 練習
- 製作一個Thing 且沒有內容的類別,並將它印出,用這個類別建立一個名為example的物件,也將它印出,值是否相同?
- 製作一個名為Thing2的新類別,並將'abc'值 指派給一個名為letters的類別屬性,印出letters
- 製作一個名為Thing3的新類別。這次指派'xyz'給一個名為letters的實例(物件)屬性。印出letters。你需要這個類別製作一個物件才能印出letters嗎?
- 製作一個名為Element的類別加入實例屬性name、symbol 與number。用這個類別建立一個物件,給他值'Hydrogen'、'H' 與 1 。並印出來。
- 用以下的鍵與值來製作一個字典: 'name':'Hydrogen,'symbol':'H','number': 1。接著使用這個字典,用Element類別建立一個名為Hydrogen的物件。
- 對Element類別定義一個名為dump()的方法,讓它印出該物件的屬性值(name、symbol、number)。用這個定義建立hydrogen物件,並使用dump()來印出它的屬性。
- 呼叫print(hydrogen)。在Element的定義中,將方法dump的名稱改為__str__,建立一個新的hydrogen物件,並再次呼叫print(hydrogen)
- 修改 Element,讓name、symbol、number變成私用。為每一個屬性定義getter特性,並回傳它的值。
方法二:
- 定義三個類別:Bear、Rabbit、Octothorpe。在這些類別中定義唯一的方法:eats(),讓他回傳'beeiers'(Bear)、'clover'(Rabbit) 與 'campers'(Octothorpe)。用各個類別建立一個物件,並印出他吃什麼。
- 定義以下類別:Laser、Claw 與 SmartPhone。它們裡面只有一個方法:does()。讓他回傳'disintegrate'(Laser)、'crush'(Claw)、'ring'(SmartPhone)。接著定義Robot類別,讓他擁有這些類別的一個實例(物件)。為Robot定義一個does()方法,印出它的零件物件作用。