mro - 方法解析順序
use mro; # enables next::method and friends globally
use mro 'dfs'; # enable DFS MRO for this class (Perl default)
use mro 'c3'; # enable C3 MRO for this class
"mro" 名稱空間提供多種工具,用於處理方法解析順序和方法快取。
這些介面僅在 Perl 5.9.5 及更高版本中可用。請參閱 CPAN 上的 MRO::Compat,以取得舊版 Perl 的前向相容實作。
可以使用 use mro
(如語法中所示)或使用以下的 "mro::set_mro" 函式來變更指定類別的 MRO。
特殊方法 next::method
、next::can
和 maybe::next::method
在透過 use
或 require
載入這個 mro
模組之前無法使用。
除了傳統 Perl 預設 MRO(深度優先搜尋,在此稱為 DFS
)之外,Perl 現在也提供 C3 MRO。Perl 對 C3 的支援是基於 Stevan Little 的模組 Class::C3 中所做的工作,而此處大部分與 C3 相關的文件都是直接從該處擷取的。
C3 是演算法名稱,其目標是在多重繼承下提供健全的方法解析順序。它最初在 Dylan 語言中引入(請參閱「請參閱」區段中的連結),然後後來採用為 Python 2.3 中新式類別的首選 MRO(方法解析順序)。最近它已被採用為 Raku 類別的「正規」MRO。
C3 運作方式是始終保留本機優先順序。這基本上表示任何類別都不會出現在其任何子類別之前。例如,採用經典菱形繼承模式
<A>
/ \
<B> <C>
\ /
<D>
標準 Perl 5 MRO 會是 (D, B, A, C)。結果是 **A** 出現在 **C** 之前,即使 **C** 是 **A** 的子類別。然而,C3 MRO 演算法會產生下列順序:(D, B, C, A),沒有這個問題。
這個範例相當簡單;對於更複雜的案例和更深入的說明,請參閱「請參閱」區段中的連結。
傳回一個陣列參考,它是給定類別的線性化 MRO。預設使用目前對該類別有效的任何 MRO,或給定的 MRO(如果指定為 $type
,則為 c3
或 dfs
)。
類別的線性化 MRO 是所有類別的有序陣列,當解析該類別上的方法時會搜尋這些類別,從類別本身開始。
如果請求的類別尚未存在,這個函式仍會成功,並傳回 [ $classname ]
請注意,UNIVERSAL
(以及 UNIVERSAL
的 MRO 的任何成員)不屬於類別的 MRO,即使所有類別都隱含繼承自 UNIVERSAL
及其父項的方法。
將給定類別的 MRO 設定為 $type
參數(c3
或 dfs
)。
傳回指定類別的 MRO(可能是 c3
或 dfs
)。
取得此類別的 mro_isarev
,傳回類別名稱的陣列參考。這些是每個「isa」指定類別名稱的類別,即使 isa 關係是間接的。MRO 程式碼會在內部使用此功能來追蹤方法/MRO 快取無效化。
與上述的 mro::get_linear_isa
一樣,UNIVERSAL
是特殊的。UNIVERSAL
(及其父類別)的 isarev 清單不包含現存的每個類別,即使所有類別在方法繼承的用途上都是後代。
傳回布林狀態,指出指定的類別名稱是否為 UNIVERSAL
本身,或透過 @ISA
繼承而成為 UNIVERSAL
的父類別之一。
此函數傳回 true 的任何類別在意義上都是「通用的」,因為所有類別都可能從中繼承方法。
遞增 PL_sub_generation
,這會使所有套件中的方法快取無效化。
使任何依賴於指定類別的類別的方法快取無效化。這通常不需要。已知的唯一情況是純 perl 程式碼會混淆方法快取,就是當您手動安裝新的常數子程式,使用唯讀純量值,例如 constant 的內部會這樣做。如果您找到其他情況,請回報給我們,讓我們可以修正或在此處記載例外。
傳回一個整數,每當套件 $classname
中的真實區域方法變更,或 $classname
的區域 @ISA
被修改時,此整數就會遞增。
這適用於執行大量類別內省的模組作者,因為這允許他們在上次查看後,快速檢查指定類別的區域屬性是否有任何重要變更。它不會在超類別中的方法/@ISA
變更時遞增。
找出實際變更仍由您負責,而且實際上可能沒有任何變更。也許自您上次檢查後的所有變更都相互抵銷,讓套件處於與之前相同的狀態。
當套件快取被實例化時,此整數通常從 1
開始。在快取根本不存在的套件上呼叫它會傳回 0
。如果套件快取被完全刪除(這並非正常情況,但如果有人執行類似 undef %PkgName::
的動作,就會發生),數字會重設為 0
或 1
,視套件被清除的完整程度而定。
這有點像 SUPER
,但它使用 C3 方法解析順序,以便在多重繼承情況下獲得更好的相容性。請注意,雖然繼承通常遵循給定類別中有效的 MRO,但 next::method
只使用 C3 MRO。
一般使用方式如下
sub some_method {
my $self = shift;
my $superclass_answer = $self->next::method(@_);
return $superclass_answer + 1;
}
請注意,您不會(重新)指定方法名稱。它強制您始終使用與您開始的方法相同的方法名稱。
當然,它可以被呼叫在物件或類別上。
它解析要呼叫哪個實際方法的方式是
首先,它確定它被呼叫的物件或類別的線性化 C3 MRO。
然後,它確定它被呼叫的上下文的類別和方法名稱。
最後,它沿著 C3 MRO 清單向下搜尋,直到到達上下文封裝類別,然後沿著 MRO 清單向下搜尋下一個與上下文封裝方法同名的方法。
找不到下一個方法將導致引發例外(請參閱下列的替代方案)。
這與 SUPER
在複雜的多重繼承下的行為有很大的不同。(當人們意識到給定類別和其父類別的 C3 線性化中的共同超類別並非總是對兩者都以相同的方式排序時,這一點就顯而易見了。)
警告:從類別外部定義的方法呼叫 next::method
在從與呼叫它的模組不同的模組中建立的子常式中使用 next::method
時有一個邊界情況。聽起來很複雜,但實際上並非如此。以下是一個無法正確運作的範例
*Foo::foo = sub { (shift)->next::method(@_) };
問題存在於指定給 *Foo::foo
全域變數的匿名子常式將在呼叫堆疊中顯示為被呼叫為 __ANON__
,而不是您可能預期的 foo
。由於 next::method
使用 caller
來尋找它被呼叫的方法的名稱,因此在這種情況下它會失敗。
但別擔心,有一個簡單的解決方案。模組 Sub::Name
將深入 perl 內部並為匿名子常式指定一個名稱。只需執行以下操作
use Sub::Name 'subname';
*Foo::foo = subname 'Foo::foo' => sub { (shift)->next::method(@_) };
事情就會順利進行。
這類似於 next::method
,但只會傳回程式碼參考或 undef
以指示不存在此名稱的進一步方法。
在簡單的情況下,它等於
$self->next::method(@_) if $self->next::can;
但有些情況下只有這個解決方案有效(例如 goto &maybe::next::method
);
Brandon L. Black,<blblack@gmail.com>
基於 Stevan Little 的 Class::C3