今天來玩玩看 class,順便紀錄一些知識。
Lets build a brief rectangular class.
class Rectangular:
""" 做一個正方形 """
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
""" 算周長 """
return 2 * (self.width + self.height)
r1 = Rectangular(5,10)
print(f"長 {r1.width}", f"寬 {r1.height}")
print(f"面積 {r1.area()}")
print(f"周長 {r1.perimeter()}")
長 5 寬 10
面積 50
周長 30
觀察上面的 class,總共有四個 attribute - width, height, area, perimeter
不能執行的 width, height 稱為 property,可以執行的 area, perimeter 稱為 method
property 和 method 都可以用 "." 的寫法呼叫,上面就叫出了方形的長寬和面積等資訊。
假如我們用 str() 觀察 r1
str(r1)
'<__main__.Rectangular object at 0x107de6b00>'
恩⋯⋯得到物件的 type 和記憶體位置等資訊,不過我們大概更想看到這個方形的長寬資訊,對吧?
這時我們可以覆寫 Rectangular 的 __str__ method
class Rectangular:
""" 做一個正方形 """
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
""" 算周長 """
return 2 * (self.width + self.height)
def __str__(self):
return f"方形長{self.width}寬{self.height}"
r1 = Rectangular(5,10)
str(r1)
'方形長5寬10'
得到比較有用的資訊了!由此得知,str() 背後其實是呼叫 __str__ 這個特殊 method
這種雙底線開頭結尾的 special method 在 Python 自成一個家族,可以多加利用,那怎麼知道有哪些勒?
上網查嚕!假如有用 VSCode 等智慧編輯器寫扣的話,打雙底線後就會跳出一堆提示給你選擇了。
再練習一個吧!國小數學課最愛考方形面積比大小了,來用 Python 寫寫看:
r2 = Rectangular(3, 5)
print(r1 > r2)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
/Users/maotingyang/python_ground/鐵人賽/class_intro.ipynb Cell 9 in <cell line: 2>()
<a href='vscode-notebook-cell:/Users/maotingyang/python_ground/%E9%90%B5%E4%BA%BA%E8%B3%BD/class_intro.ipynb#X34sZmlsZQ%3D%3D?line=0'>1</a> r2 = Rectangular(3, 5)
----> <a href='vscode-notebook-cell:/Users/maotingyang/python_ground/%E9%90%B5%E4%BA%BA%E8%B3%BD/class_intro.ipynb#X34sZmlsZQ%3D%3D?line=1'>2</a> print(r1 > r2)
TypeError: '>' not supported between instances of 'Rectangular' and 'Rectangular'
出槌啦!看來這方形物件之間是不能直接比大小的,當然也可以呼叫 area method 出來比啦⋯⋯
但我就是想少打幾個字,直接比較嘛!
class Rectangular:
""" 做一個正方形 """
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
""" 算周長 """
return 2 * (self.width + self.height)
def __str__(self):
return f"方形長{self.width}寬{self.height}"
def __gt__(self, other):
if isinstance(other, Rectangular):
return self.area() > other.area()
else:
return NotImplemented
r1 = Rectangular(5,10)
r2 = Rectangular(3,5)
print(r1 > r2)
print(r1 < r2)
True
False
__gt__ 是 greater than,我們幫 Rectangular 自定義了它的大於判定,可以直接比較物件啦,開勳~
等等!小於沒定義啊!最後一行的小於怎麼不會出錯?
Python 很聰明的啦!發現沒實作小於但有大於,那就自動改成 r2 > r1 來比嚕!
不過大於等於就會掛掉了QQ
r1 >= r2
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
/Users/maotingyang/python_ground/鐵人賽/class_intro.ipynb Cell 13 in <cell line: 1>()
----> <a href='vscode-notebook-cell:/Users/maotingyang/python_ground/%E9%90%B5%E4%BA%BA%E8%B3%BD/class_intro.ipynb#X42sZmlsZQ%3D%3D?line=0'>1</a> r1 >= r2
TypeError: '>=' not supported between instances of 'Rectangular' and 'Rectangular'
看來大於等於就只好再去找對應的 special method 來刻了。那等於呢?自己試試看吧!
假設你之前學過 Java,大概會覺得 Python 這種用 "." 直接呼叫 property 的行爲很糟糕⋯⋯
因為,假如今天要多加一個限制:方形的長寬不得小於零,Java 的玩家可能會先把 width, height 改成 _width, _height,(private property,無法直接用 "." 讀取),然後寫個 set_width 之類的 method 讓使用者設定長寬,在這個 method 裡面設定檢查機制,小於零不給過。但你整份程式碼已經寫了一堆的 r1.width ⋯⋯
又要大改了。
放心,Python 這種寫法有考量到上述情況,我們可以用 decorator 來解決!
class Rectangular:
def __init__(self, width, height):
self._width = width
self._height = height
def __repr__(self):
return '方形長寬({0}, {1})'.format(self.width, self.height)
@property
def width(self):
"""加了 property decorator, 這個 width method 變成 width property!"""
return self._width
@width.setter
def width(self, width):
""" 針對 width property 做一些檢查 """
if width <= 0:
raise ValueError('Width must be positive.')
self._width = width
@property
def height(self):
"""加了 property decorator, 這個 height method 變成 height property!"""
return self._height
@height.setter
def height(self, height):
""" 針對 height property 做一些檢查 """
if height <= 0:
raise ValueError('Height must be positive.')
self._height = height
r3 = Rectangular(5, 10)
r3
方形長寬(5, 10)
r3.width = -100
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
/Users/maotingyang/python_ground/鐵人賽/class_intro.ipynb Cell 18 in <cell line: 1>()
----> <a href='vscode-notebook-cell:/Users/maotingyang/python_ground/%E9%90%B5%E4%BA%BA%E8%B3%BD/class_intro.ipynb#X51sZmlsZQ%3D%3D?line=0'>1</a> r3.width = -100
/Users/maotingyang/python_ground/鐵人賽/class_intro.ipynb Cell 18 in Rectangular.width(self, width)
<a href='vscode-notebook-cell:/Users/maotingyang/python_ground/%E9%90%B5%E4%BA%BA%E8%B3%BD/class_intro.ipynb#X51sZmlsZQ%3D%3D?line=15'>16</a> """ 針對 width property 做一些檢查 """
<a href='vscode-notebook-cell:/Users/maotingyang/python_ground/%E9%90%B5%E4%BA%BA%E8%B3%BD/class_intro.ipynb#X51sZmlsZQ%3D%3D?line=16'>17</a> if width <= 0:
---> <a href='vscode-notebook-cell:/Users/maotingyang/python_ground/%E9%90%B5%E4%BA%BA%E8%B3%BD/class_intro.ipynb#X51sZmlsZQ%3D%3D?line=17'>18</a> raise ValueError('Width must be positive.')
<a href='vscode-notebook-cell:/Users/maotingyang/python_ground/%E9%90%B5%E4%BA%BA%E8%B3%BD/class_intro.ipynb#X51sZmlsZQ%3D%3D?line=18'>19</a> self._width = width
ValueError: Width must be positive.
成功!經過 decorator 的檢查,現在長寬可不能亂設成負數了!至於什麼是 decorator,交給你查嚕!
不過要注意,Python 中並沒有真正的 private property,底線+property 的寫法 (_width) 只是明示大家:這個 propery 是 private 的歐!希望大家別直接去改動他!
手貝戈戈想改的話還是可以⋯⋯
r3._width = -100
r3.width
-100
再進一步,我們可以在 init 物件的時候就做長寬的檢查:
class Rectangular:
def __init__(self, width, height):
self._width = None
self._height = None
# 呼叫 accessor methods 設定長寬
self.width = width
self.height = height
def __repr__(self):
return '方形長寬({0}, {1})'.format(self.width, self.height)
@property
def width(self):
return self._width
@width.setter
def width(self, width):
if width <= 0:
raise ValueError('Width must be positive.')
self._width = width
@property
def height(self):
return self._height
@height.setter
def height(self, height):
if height <= 0:
raise ValueError('Height must be positive.')
self._height = height
r3 = Rectangular(4,10)
r3
方形長寬(4, 10)
r4 = Rectangular(0,10)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
/Users/maotingyang/python_ground/鐵人賽/class_intro.ipynb Cell 24 in <cell line: 1>()
----> <a href='vscode-notebook-cell:/Users/maotingyang/python_ground/%E9%90%B5%E4%BA%BA%E8%B3%BD/class_intro.ipynb#W4sZmlsZQ%3D%3D?line=0'>1</a> r4 = Rectangular(0,10)
/Users/maotingyang/python_ground/鐵人賽/class_intro.ipynb Cell 24 in Rectangular.__init__(self, width, height)
<a href='vscode-notebook-cell:/Users/maotingyang/python_ground/%E9%90%B5%E4%BA%BA%E8%B3%BD/class_intro.ipynb#W4sZmlsZQ%3D%3D?line=3'>4</a> self._height = None
<a href='vscode-notebook-cell:/Users/maotingyang/python_ground/%E9%90%B5%E4%BA%BA%E8%B3%BD/class_intro.ipynb#W4sZmlsZQ%3D%3D?line=4'>5</a> # 呼叫 accessor methods 設定長寬
----> <a href='vscode-notebook-cell:/Users/maotingyang/python_ground/%E9%90%B5%E4%BA%BA%E8%B3%BD/class_intro.ipynb#W4sZmlsZQ%3D%3D?line=5'>6</a> self.width = width
<a href='vscode-notebook-cell:/Users/maotingyang/python_ground/%E9%90%B5%E4%BA%BA%E8%B3%BD/class_intro.ipynb#W4sZmlsZQ%3D%3D?line=6'>7</a> self.height = height
/Users/maotingyang/python_ground/鐵人賽/class_intro.ipynb Cell 24 in Rectangular.width(self, width)
<a href='vscode-notebook-cell:/Users/maotingyang/python_ground/%E9%90%B5%E4%BA%BA%E8%B3%BD/class_intro.ipynb#W4sZmlsZQ%3D%3D?line=15'>16</a> @width.setter
<a href='vscode-notebook-cell:/Users/maotingyang/python_ground/%E9%90%B5%E4%BA%BA%E8%B3%BD/class_intro.ipynb#W4sZmlsZQ%3D%3D?line=16'>17</a> def width(self, width):
<a href='vscode-notebook-cell:/Users/maotingyang/python_ground/%E9%90%B5%E4%BA%BA%E8%B3%BD/class_intro.ipynb#W4sZmlsZQ%3D%3D?line=17'>18</a> if width <= 0:
---> <a href='vscode-notebook-cell:/Users/maotingyang/python_ground/%E9%90%B5%E4%BA%BA%E8%B3%BD/class_intro.ipynb#W4sZmlsZQ%3D%3D?line=18'>19</a> raise ValueError('Width must be positive.')
<a href='vscode-notebook-cell:/Users/maotingyang/python_ground/%E9%90%B5%E4%BA%BA%E8%B3%BD/class_intro.ipynb#W4sZmlsZQ%3D%3D?line=19'>20</a> self._width = width
ValueError: Width must be positive.
成功啦!現在物件初始化就會檢查長寬,這才對嘛!
至於 accessor method 是什麼?留給你去查吧!今天篇幅也太長了!
咱們~明~天~見~