[note] Python OOP
常用方法
Global Methods
Build-in Functions @ Python Doc
type(obj)  # 取得建立該 instance 的 Class
isinstance(obj, Class)
# 如果屬性是會動態改變的
getattr(obj, name, [default_value]) # 拿不到該屬性時不會噴 error
setattr(obj, name, value)
Class
基本使用
- 使用 
__init__初始化物件 - 定義類別屬性(class attribute):直接定義在 class 中
 - 定義類別方法(class method):使用 
@classmethod,第一個參數會是 class 本身,常被取名為cls - 定義實體變數(instance variable):在 
__init__中建立 - 定義實體方法(instance method):直接定義  在 class 中,第一個參數會是物件實體(instance)本身,常被取名為 
self - 定義靜態方法(static method):使用 
@staticmethod,預設第一個參數不會帶入任何東西,單純就是個函式 
class Dog:
    # 建立類別屬性(Class attribute)
    species = "Canis lupus familiaris"
    # 物件初始化
    def __init__(self, name, breed, age):
        # 建立實體變數(instance variable)
        self.name = name
        self.breed = breed
        self.age = age
    # 建立類別方法(Class Method)
    @classmethod
    def all(cls):
        return f"All dogs are {cls.species}"
    # 建立實體方法(Instance Method)
    def bark(self):
        return f"{self.name} is barking!"
   # 建立靜態方法(Static Method)
    @staticmethod
    def info():
        return "Dogs are loyal animals."
# 建立物件
buddy = Dog("Buddy", "Golden Retriever", 5)
type(Dog)  # <class 'type'>
type(buddy)  # <class '__main__.Dog'>
type(Dog.bark)  # function
type(Dog.all)   # method
type(buddy.bark)  # method
isinstance(buddy, Dog) # True
# 存取 Class Attribute 和呼叫 Class Method
Dog.species # Dog.species
Dog.all() # 'All dogs are Canis lupus familiaris'
# 存取 Instance variables 和呼叫 Instance Method
buddy.name # 'Buddy'
buddy.bark() # 'Buddy is barking!'
# 呼叫 Static Method
Dog.info()  # 'Dogs are loyal animals.'
buddy.info()  # 'Dogs are loyal animals.'
在 Python 中,查找 object attribute 的邏輯如下,如果該 object 本身就有該屬性,則會直接拿該屬性的值,例如 buddy.name;如果該 object 本身沒有該屬性,則會看看它的 class 中有沒有該屬性,例如 buddy.species 會拿到的是 class attribute 的值(即,Dog.species);如果 instance attribute 和 class attribute 都找不到,而且也不是用 getattr 有預設值的話,則會噴 AttributeError 的錯誤。
這樣的邏輯也同樣使用在類別中的函式或方法,也就是說,當物件實體呼叫了一個方法,如果這個實體本身就含有該實體方法(可以用 vars 或 __dict__ 查看),則會直接使用這個實體方法;如果該實體中沒有這個方法,則會到類別中去找有沒有同樣名稱的方法,有的話,就會去呼叫類別中的這個方法;如果還是沒有,會在往上找這個類別是不是繼承其他類別而來,是的話,其上層有沒有這個方法。
如果好奇 Python 會怎麼樣一層一層往上找,可以使用 mro() 這個方法(Method Resolution Order),例如,Dog.__mro__ 會的得到 [__main__.Dog, object]。
使用 __slots__ 明確定義該物件的屬性(instance variable)
預設使用者可以自由在物件中添加新的屬性,如果希望這個物件的 instance variables 是固定而不能被任意添加的,則可以用 __slots__:
- 沒辨法自由在該 instance 添加新的 instance variable,例如,使用 
john.height = 180會拋出AttributeError - 沒辦法使用 
vars或__dict__檢視整個物件的實體變數,例如,使用vars(john)會拋出TypeError,因為__dict__屬性已經不能存在 
class Person:
    __slots__ = ["name", "age"]
    def __init__(self, name, age):
        self.name = name
        self.age = age
john = Person("John", 78)
john.height = 180  # AttributeError: 'Person' object has no attribute 'height'
vars(john) # TypeError: vars() argument must have __dict__ attribute
Getter 和 Setter
可以使用 @property 和 @<property>.setter 來設定 instance variable 的 getter 和 setter,例如:
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age
    # 使用 @property 定義 age 的 getter
    @property
    def age(self):
        return self.age
    # 使用 @xxx.setter 定義 age 的 setter
    @age.setter
    def age(self, value):
        if value < 0:
            raise ValueError("Age cannot be negative")
        self.age = value
其他
在 Python 中 class 也被稱作 type
class Person:
    def __init__(self, name, age, height):
        self.name = name
        self.age = age
        self.__height = height
john = Person("John", 78, 182)
type(john) # __main__.Person
type(Person) # type
- class 的 
type也是type - type 的 
type也是type - 所以:
type(Person) == type(type) == type 
定義 __ 開頭的 instance variable
如果使用 __ 作為 instance variable 的開頭,該 instance variable 的名稱會被加上 class name 當作 prefix。從下面的例子可以看到,原本 __height 的變數,其 instance variable 的名稱變成 _Person__height:
class Person:
    def __init__(self, name, age, height):
        self.name = name
        self.age = age
        self.__height = height
john = Person("John", 78, 182)
vars(john) # {'name': 'John', '_age': 78, '_Person__height': 182}
Instance
取值
class Person:
  def __init__(self, name, age):
    self.name = name
    self.age = age
john = Person("John", 24)
# 使用 "."
john.age # 24
john.foo # AttributeError: 'Person' object has no attribute 'foo'
# 使用 getattr
# getattr(obj, name, [default_value]) # 拿不到該屬性時不會噴 error
getattr(john, "age")  # 24
getattr(john, "foo")  # AttributeError: 'Person' object has no attribute 'foo'
getattr(john, "foo", "default_value")  # "default_value"
設值
# 使用 "."
john.height = 178
# 使用 setattr
# setattr(obj, name, value)
setattr(john, "height", 182)
檢視物件(vars、__dict__)
buddy = Dog("Buddy", "Golden Retriever", 5)
bella = Dog("Bella", "Labrador", 3)
# 使用 vars,它會回傳該物件的 __dict__
vars(buddy)     # {'name': 'Buddy', 'breed': 'Golden Retriever', 'age': 5}
# 直接使用 __dict__
buddy.__dict__  # {'name': 'Buddy', 'breed': 'Golden Retriever', 'age': 5}