14. 進階主題
• 學習目標
– 運用描述器
– 實作裝飾器
– 定義 meta 類別
– 使用相對匯入
描述器
• 擁有 __get__() 方法,以及選擇性的
__set__()、__delete__() 方法
• 當描述器成為某個類別的屬性成員時,對
於類別屬性或者其實例屬性的取得、設定
或刪除,將會交由描述器來決定如何處理
• 當 Descriptor 被指定給 Some 類別的 x
屬性時,對於 Some 實例 s 的屬性取值、
指定或刪除,分別相當於進行了以下動作:
• 對於 Some.x 這個取值動作,則相當於:
• 資料描述器可以攔截對實例的屬性取得、
設定與刪除行為
• 非資料描述器,是用來攔截透過實例取得
類別屬性時的行為
• 若想控制可以指定給物件的屬性名稱,可
以在定義類別時指定 __slots__
• 這個屬性要是個字串清單,列出可指定給
物件的屬性名稱
• 如果類別定義時指定了 __slots__,那麼
從類別建構出來的實例就不會具有
__dict__ 屬性
• __slots__ 中的屬性,Python 會將之實
作為描述器
• __slots__ 屬性最好作為類別屬性來使用
• 父類別中定義的 __slots__,僅可以透過
父類別來取得,而子類別的 __slots__ 則
僅可以透過子類別來取得
• 若父類別中沒有定義 __slots__,子類別
即使定義了__slots__,以子類別建構出
來的實例,仍然會具有 __dict__ 屬性
• 如果父類別定義了__slots__,而子類別
沒有定義自己的 __slots__,子類別建構
出來的實例也會有 __dict__
• 物件本身可以決定存取屬性的行為
• __getattribute__() 一但定義,任何
屬性的尋找都會被攔截,即使是那些
__xxx__ 的內建屬性名稱
• __getattr__() 的作用,是作為尋找屬
性的最後一個機會
• 取得屬性的順序
– 實例的 __getattribute__()
– 資料描述器的 __get__()
– 實例的 __dict__
– 非資料描述器的 __get__()
– 實例的 __getattr__()
• __setattr__() 的作用,在於攔截所有
對實例的屬性設定
• 設定屬性順序記憶
– 實例的 __setattr__()
– 資料描述器的 __set__()
– 實例的 __dict__
• __delattr__()的作用,在於攔截所有對
實例的屬性設定
• 刪除屬性順序記憶
– 實例的__delattr__()
– 資料描述器的__delete__()
– 實例的__dict__
函式裝飾器
• 裝飾器本質上就是一個函式可接受函式且
傳回函式
• 假設你設計了一個點餐程式…
• 一個函式可以接受函式並傳回函式
• 如果裝飾器語法需要帶有參數,用來作為
裝飾器的函式,必須先以指定的參數執行
一次,傳回函式物件再來裝飾指定的函式
• 除了對函式進行裝飾之外,也可以對類別
作裝飾,也就是所謂類別裝飾器
• 除了使用函式來定義裝飾器之外,也可以
使用類別來定義裝飾器
• 若要使用類別來定義函式裝飾器:
• 若要以定義類別方式,來對函式進行裝飾:
• 若要以定義類別的方式,實作對類別的裝
飾器:
• 可以對類別上定義之方法進行裝飾
• 可以選擇使用函式或者類別來實作
• 方法的第一個參數總是類別的實例本身
• 讓 @log 裝飾的對象,不限於可接受兩個引
數的方法
認識 type 類別
• 每個物件實例本身都有個 __class__ 屬性
• 類別本身也有個 __class__ 屬性
• 在類別上呼叫 __call__() 會如何呢?
• 使用 type 類別建構類別時,必須指定三個
引數
– 類別名稱(字串)
– 類別的父類別(tuple)
– 類別的屬性(dict)
• 物件是類別的實例,而類別是 type 的實例
• 如果有方法能介入 type 建立實例與初始化
的過程,就會有辦法改變類別的行為,這
就是 meta 類別的基本概念
• type 是個類別,那麼可以繼承嗎?
• 可以在使用 class 定義類別時,指定
metaclass 為 SomeMeta:
• 繼承了 type 的類別可以作為 meta 類別
• metaclass 是個協定
• 若指定了 metaclass 的類別, Python 在
剖析完類別定義後, 會使用指定的
metaclass 來進行類別的建構與初始化
• 如果使用 class 定義類別時繼承某個父類
別,亦想要指定 metaclass
• 使用類別建立物件時:
• 若想改變一個類別建立實例與初始化的流
程,則可以在定義 meta 類別時定義
__call__()方法:
• meta 類別就是 type 的子類別
• 藉由 metaclass = MetaClass 的協定,
可在類別定義剖析完後,繞送至指定的
meta 類別
• 可以定義 meta 類別的 __new__()方法,
決定類別如何建立
– 定義 meta 類別的 __init__(),可以決定類
別如何初始
– 定義 meta 類別的__call__()方法,決定若
使用類別來建構物件時,該如何進行物件的建
立與初始
• metaclass 並不僅僅可指定類別
• 可以指定的對象可以是類別、函式或任何
的物件,只要它具有 __call__() 方法
• 可以定義類別的 __abstractmethods__,
指明某些特性是抽象方法
• 子類別不會看的到父類別的
__abstractmethods__
• Python 實際上還支援相對匯入(Relative
import)
• 如果想在 xyz.py 中匯入 abc 模組,在
xyz.py 中不能寫 import abc,在
Python 3 中這會是絕對匯入
• 實際上會匯入的是標準程式庫的 abc 模組
• 如果想在 xyz.py 中匯入 mno 模組,在
xyz.py 中不能寫 import mno
• 這會引發 ImportError,指出沒有 mno
這個模組
• 如果要使用絕對匯入,必須撰寫 import
pkg1.mno
• 若要使用相對匯入,則可以撰寫 from .
import mno
• 如果想使用的是 mno 模組中的 foo()函式,
使用相對匯入的話,還可以撰寫
from .mno import foo 這樣的方式
• 在某個程式中,若 import pkg1 的話,
會執行__init__.py 的內容
• 可以在 pkg1 的 __init__.py 中撰寫:
• 只要 import pkg1,就可以直接使用
pkg1.abc、pkg2.mno、pkg1.xyz 來使
用模組了
• 如果想要 import pkg1 之後,可以直接
使用 pkg1.abc、pkg2.mno、pkg1.xyz
• 還能直接使用 pkg1.sub_pkg.foo、
pkg1.sub_pkg.orz 模組
• 那麼在 pkg1 的 __init__.py 中,可以撰寫:
• 而在 pkg1.sub_pkg 的 __init__.py 中,可
以撰寫:
• 相對匯入只能用在套件之中,如果試圖使
用 python 直譯器執行的某個模組中含有
相對匯入,會引發 SystemError

進階主題