iT邦幫忙

2022 iThome 鐵人賽

DAY 3
0

今天來玩玩看 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 都可以用 "." 的寫法呼叫,上面就叫出了方形的長寬和面積等資訊。

Python 內建的特殊 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 來刻了。那等於呢?自己試試看吧!

直接給人 r1.width 改長寬好像怪怪的⋯⋯

假設你之前學過 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 是什麼?留給你去查吧!今天篇幅也太長了!

咱們~明~天~見~


上一篇
條件控制:while/else 與 for/else
下一篇
Python 裡的資源回收
系列文
小青蛇變大蟒蛇——進階Python學起來!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言