中英譯詞對照:
類別實例(class instance)
Metaprogramming 行前提示
-
Metaprogramming 很少用到。除非你要寫 framework 或 library,一般的應用程式是用不到的。
-
不要學了 Metaprogramming 就想炫技。每個程式都想用 Metaprogramming 來寫,這會讓你的程式可讀性很差(包含一個月後回來看程式的你自己)。
-
寫程式時,謹守 DRY 原則:Don’t Repead Yourself
-
那為什麼還要學?因為這是理解 Python OOP 運作的重要入口。
-
不要急。看不懂就多看幾次;未來感覺困惑時,一樣再回來複習。
-
動手實作。這個我們強調很多次了,你無法觀看游泳影片就學會游泳。
Decorators and Descriptors
creation vs. initiation
創建 vs. 初始化
創建:類別創建的過程。一定義即創建。
# 以下的程式創建一個名為 MyClass 的類別:
class MyClass:
初始化:類別初始化的過程 類別的初始化是透過使用
__init__()
方法來完成
class MyClass:
# 以下的程式,透過使用 `__init__()` 方法來初始化類別:
def __init__(self, name, age):
self.name = name
self.age = age
#創建:類別創建的過程
class MyClass:#初始化:類別初始化的過程 類別的初始化是透過使用
__init__()
方法來完成
def__init__
(self, name, age):
我試著用 LaTeX 數學語法來繪製他們之間的關係:
__new__
行前提示
the
__init__
method is called (bound to the new object) 綁定物件Python 會自動執行
__new__
然後__init__
,兩者參數必須相同。你也可以自行呼叫,但必須自行逐一呼叫。
__new__(xxx, x, y)
x, y 會被忽略,__new__()
不接受參數,會直接將參數往後傳遞,在 __init__
中接收 __new__()
傳過來的參數,然後做相關處理。
需使用 super() 才會繼承父物件。object 不會。
範例證明:person => student
__new__
執行完後,之後會執行(但 Python 內部有些處理) __init__
,兩者參數必須相同。
為什麼不直接用 ?? 就好?
-
Python 會幫你自動執行,你不必重覆寫。
-
我們可以在
__new__
中增加我們想要達成的事
示範 __new__
中增加我們想要達成的事,不透過 __init__
示範 __new__
__init__
回傳值不同
__new__
__init__
回傳值相同時,Python 系統呼叫 __new__
時,自動跟著呼叫 __init__
__new__
__init__
回傳值不同時,Python 系統呼叫 __new__
時,不再自動呼叫 __init__
實作 __new__
的基本元素
class Person:
def __new__(cls, name, age):
# Do somthing
print(f'Person: Instantiating {cls.__name__}...')
# create the object to return
# instance = object.__new__(cls)
instance = super().__new__(cls) ## <=== super() 才能繼承 parent 特性
# Do more things with instance
# return the object
return instance
def __init__(self, name):
print(f'Person: Initializing instance...')
self.name = name
羅馬 類別是怎麼建成的?
行前提示
-
exec
-
type(object) → the object’s type
-
type(name, bases, dict) → a new type
-
請參考 Python 官網有關 class 的 中文說明:
視覺化程式
用 Python Tutor 視覺化逐行執行程式碼 看看:
內心戲四部曲
Python Tutor 只能直接秀出關係圖,無法像老師那樣逐步說明。沒關係,我們自己腦補:
-
Python 從 \color{#FF7F27}{class\ body} 部分(上圖橘框)提取資料(就是一長串文字,但它是有效的代碼)。
-
創建一個新字典,作為新類別的命名空間(dict 作為 namespace)。
-
body code 在上述命名空間內執行,從而填入命名空間。
和 module 中執行程式的思維相同:執行程式碼後,module namespace(模組命名空間)將包含 planet、name、
__init__
。 -
使用
name of the class
,the base classes
&the populated dictionary
,創建一個新的類別實例。type(class_name, class_bases, class_dict)
- ‘Person’ in globals() # True
手工業又來了
和之前的課程相同。老師為了示範整個執行流程,手工一步一步打造類似的步驟。
因為之後會有簡單直接的做法,這裡當作理解流程的參考即可。
Step 1. 提取文字
class_name = 'Circle'
class_body = """
def __init__(self, x, y, r):
self.x = x
self.y = y
self.r = r
def area(self):
return math.pi * self.r ** 2
"""
Step 2. 創建 dict 作為 namespace
class_bases = () # defaults to object
class_dict = {}
Step 3. exec 執行 code,填入 namespace dict
exec(class_body, globals(), class_dict)
class_dict
# 以下為輸出
{'__init__': <function __main__.__init__(self, x, y, r)>,
'area': <function __main__.area(self)>}
Step 4. 呼叫 type 建立 class
type(name, bases, dict) → a new type
Circle = type(class_name, class_bases, class_dict)
Circle
# 輸出:__main__.Circle
type(Circle)
# 輸出:type
Circle.__dict__
# 以下為輸出
mappingproxy({'__init__': <function __main__.__init__(self, x, y, r)>, ## <=== class_body
'area': <function __main__.area(self)>, ## <=== class_body
'__module__': '__main__',
'__dict__': <attribute '__dict__' of 'Circle' objects>,
'__weakref__': <attribute '__weakref__' of 'Circle' objects>,
'__doc__': None})
複習:type 的兩種呼叫方式
- 一個參數:回傳物件的 type 時
type(Circle)
# 輸出:type
- 三個參數:創建 class 時(i.e. 創建 type 的 instance)
Circle = type(class_name, class_bases, class_dict)
客製化 type - 透過繼承 type
簡單的說,就是繼承 type,然後在
__new__
裡面,做客製化的部分。class CustomType(type): # 繼承 type
CustomType(name, bases, dict) # 呼叫 CustomType 產生 class(一樣三參數)。
CustomType 也是 metaclass
這節老師又以手工業方式,示範了手動 4步驟,主要的不同如下,其他我們就省略跳過了。
class CustomType(type):
...
- Circle = type(name, bases, dict)
+ Circle = CustomType('Circle', (), class_dict)
meta class 元類別
老做手工業當然很麻煩,終於講到正規的作法了(看老師自己也鬆了一口氣)。
class Person(metaclass = MyType):
其實我們一般的 class,就是 Python 預設,不用寫出來 class O_O(metaclass = type):
開始之前
Use those very rarely. They are not used frequently.
Don’t invent a problem just because you’ve got a solution.
整個 idea
class MyType(type):
# mcls: metaclass(MyType)
# name: name of class(Person)
def __new__(mcls, name, bases, cls_dict):
# Do something
# create the class itself via delegation
new_class = super().__new__(mcls, name, bases, cls_dict)
# Do more thins
# and return the new class
return new_class
# Do all the manual steps: name, code, class dict, bases
# then calls MyType(name, bases, cls_dict)
class Person(metaclass = MyType):
def __init__(self, name):
self.name = name
實際範例:
class CustomType(type):
def __new__(mcls, name, bases, class_dict):
print(f'Using custom metaclass {mcls} to create class {name}...') ## <=== Do something
cls_obj = super().__new__(mcls, name, bases, class_dict)
cls_obj.circ = lambda self: 2 * math.pi * self.r ## <=== Do more things
return cls_obj
class Circle(metaclass=CustomType): ## <=== Custom Type
def __init__(self, x, y, r):
self.x = x
self.y = y
self.r = r
def area(self):
return math.pi * self.r ** 2
# 輸出:Using custom metaclass <class '__main__.CustomType'> to create class Circle...
檢驗
Circle
# 輸出:__main__.Circle
vars(Circle)
# 以下為輸出:標註處是 Python metaclass 自動幫我們處理的
mappingproxy({'__module__': '__main__',
'__init__': <function __main__.Circle.__init__(self, x, y, r)>, ## <===
'area': <function __main__.Circle.area(self)>, ## <===
'__dict__': <attribute '__dict__' of 'Circle' objects>,
'__weakref__': <attribute '__weakref__' of 'Circle' objects>,
'__doc__': None,
'circ': <function __main__.CustomType.__new__.<locals>.<lambda>(self)>}) ## <===
c = Circle(0, 0, 1)
print(c.area()) # 3.141592653589793
print(c.circ()) # 6.283185307179586
適用情境?
compile 時,Python 看到 class,就 create
a class is an instance of type
type 是專門用來 create 其他 class
type 有兩種呼叫方式:
-
單一參數:傳回 object’s type
-
三參數(name, bases, dict):建立新的 type
help(type)
## 以下為輸出
class type(object)
| type(object_or_name, bases, dict)
| type(object) -> the object's type
| type(name, bases, dict) -> a new type
| ...
__new__
和 __init__
通常只需實作一個,而且是 __new__
__init__
__init__
是類別的初始化方法,通常用來為類別實例設定屬性初始值。它會在每個類別實例被建立時自動呼叫。
__init__
方法的參數是 self
。self
是類別實例本身的參考,可用來在方法中存取類別實例的屬性和方法。
以下程式定義一個 Person
類別,並在 __init__
方法中設定 name
和 age
屬性:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
要建立 Person
類別的實例,可以使用 Person()
語法。以下程式建立一個名為 john
的年齡為 30 歲的 Person
實例:
john = Person("John", 30)
__new__
__new__
是類別的特殊方法,它在類別實例被建立之前被呼叫。
-
__new__
方法負責建立類別實例,並返回類別實例。 -
__init__
方法在__new__
方法之後被呼叫,並對類別實例進行初始化。
__new__
方法沒有任何參數。
__new__
方法必須返回類別實例。
__new__
方法可用來控制類別實例的建立。
以下程式碼定義一個類別 Person,並在 __new__
方法中確保類別實例的年齡必須大於 18 歲:
class Person:
def __new__(cls, name, age):
if age < 18:
raise ValueError("Age must be greater than 18")
return super().__new__(cls)
要建立 Person 類別的實例,可以使用 Person() 語法。例如,以下程式碼會引發 ValueError 異常,因為 age 小於 18 歲:
john = Person("John", 17)
要建立 Person 類別的實例,可以使用 Person(name, age) 語法。例如,以下程式碼會建立一個名為 john 的年齡為 21 歲的 Person 實例:
john = Person("John", 21)
__new__
方法可用來在類別實例被建立時執行任何必要的初始化。