Try   HackMD

python self staticmethod classmethod dataclass 理解

tags: Python, self, staticmethod, classmethod, dataclass, 工廠模式, 工廠
  • 關於 self
    • Python Class 裡的 self 會回傳物件本身 (根據物件位址並自動 derefference 為物件)。
    • self 有點類似 linux kernel 的 macro 中的 container_of,或 linux kernel 的 macro 中 list_entry
      ​​​​​​​​// list_entry 就是 container_of,
      ​​​​​​​​// 放在 list.h 中,作為 list API 為了命名統一而重新命名
      ​​​​​​​​// 用來獲取「封裝 `struct list_head` 的
      ​​​​​​​​//  container (也是一個 `struct`) 的『address』」 
      ​​​​​​​​#define list_entry(ptr, type, member) \
      ​​​​​​​​        container_of(ptr, type, member)
      
      所以用 linux kernel 的風格來理解的話,self 就是 The object of object_entry
      由以下程式碼印出 self 訊息,可見 self 就是存於位址 (0x7ffbf95e94c0) 的 calss A 的物件。我所說的 object_entry 就是那段記憶體位址 (0x7ffbf95e94c0)。
      ​​​​​​​​class A():
      ​​​​​​​​    def __init__(self):
      ​​​​​​​​        print(self)
      ​​​​​​​​        print(type(self))
      
      ​​​​​​​​# use class `A` to implement `objA` which is a object of calss `A`
      ​​​​​​​​objA = A()
      ​​​​​​​​# stdout: <__main__.A object at 0x7ffbf95e94c0>
      ​​​​​​​​# stdout: <class '__main__.A'>
      
    • 一般情況,class 中所有 method 的第一個參數都會被自動傳入 self
    • 而 method 的第一個參數 self 只是官方慣用命名,其實可以改成任何變數名稱,例如下方範例中,第 5 行命名為 this、第 8 行命名為 object_entry
      有趣的是,__init__() 中定義 class 成員是為 self.value,但實際上是儲存成 object_A.value = 0,因此第 6 行 set_value() 可以用 this.value 正確存取 object_A.value
    • method 會被傳入 the object of object_entry,也就意味只有當 class 實作成 object 之後,才能用 object 呼叫這個 method。因此例外需求就產生了,被加上 @staticmethod 的 method 不用建立物件就能透過類別直接呼叫。而加上 @classmethod表面上不用建立物件」可以透過類別直接呼叫。
  • 關於 staticmethod
    • 有點類似把 class method 當作一個 package api function
    • 由於 staticmethod 沒有 self 參數,因此無法更動或讀取物件內的任何 member 或 method
    • 感覺不太實用? 畢竟 Python 實務上通常會把 api 包成一個 package file,而非將各種 api 包進 class。
  • 關於 classmethod
    • 第一個參數會自動帶入 The object of class_entry,官方預設命名為 cls
    • 實務上,classmethod 要用 cls 生成一個物件,並 return 該物件。一般的 class 是回傳一個初始化物件,而 classmethod 生成物件後,可對該物件作額外操作或配置,然後再回傳。
    • 可用於「工廠模式 Factory Pattern
class A(): def __init__(self): self.value = 0 def set_value(this, value): this.value = this.encryption(value) def get_value(object_entry): '''以 linux kernel 風格來說, self 其實就是 object of class_entry ''' return object_entry.value @staticmethod def encryption(value): '''就是一個普通的 function 第一個參數並不會被自動帶入 object of object address, 也不會被帶入 object of class address''' return value+value @classmethod def customize_class(cls, setvalue): '''以 linux kernel 風格來說, cls 其實就是 object of class_entry ''' customize_object = cls() customize_object.set_value(setvalue) return customize_object
object_A = A()
print(object_A.get_value()) # stdout: 0

object_A.set_value(50)      # object_A.value = 50 + 50
print(object_A.get_value()) # stdout: 100

object_A_prime = object_A.customize_class(500)
# object_A_prime.value = 500 + 500
print(object_A_prime.get_value())
# stdout: 1000