跳至主要内容

[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}

其他

檢視 Class 繼承關係

__bases____mor__
  • 使用 __bases__ 可以檢視該 Class 的上層 Class
  • 使用 __mor__ 可以檢視該 Class 往上的所有 Classes
class Animal:
pass


class Dog(Animal):
pass


class Breed(Dog):
pass


Dog.__bases__ # (<class '__main__.Animal'>,)
Dog.__mro__ # (__main__.Dog, __main__.Animal, object)

Breed.__bases__ # (<class '__main__.Dog'>,)
Breed.__mro__ # (__main__.Breed, __main__.Dog, __main__.Animal, object)

也可以用來看內建類別的繼承關係:

TypeError.__mro__  # (<class 'TypeError'>, <class 'Exception'>, <class 'BaseException'>, <class 'object'>)

資料來源

討論