目錄

名稱

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::methodnext::canmaybe::next::method 在透過 userequire 載入這個 mro 模組之前無法使用。

C3 MRO

除了傳統 Perl 預設 MRO(深度優先搜尋,在此稱為 DFS)之外,Perl 現在也提供 C3 MRO。Perl 對 C3 的支援是基於 Stevan Little 的模組 Class::C3 中所做的工作,而此處大部分與 C3 相關的文件都是直接從該處擷取的。

什麼是 C3?

C3 是演算法名稱,其目標是在多重繼承下提供健全的方法解析順序。它最初在 Dylan 語言中引入(請參閱「請參閱」區段中的連結),然後後來採用為 Python 2.3 中新式類別的首選 MRO(方法解析順序)。最近它已被採用為 Raku 類別的「正規」MRO。

C3 如何運作

C3 運作方式是始終保留本機優先順序。這基本上表示任何類別都不會出現在其任何子類別之前。例如,採用經典菱形繼承模式

   <A>
  /   \
<B>   <C>
  \   /
   <D>

標準 Perl 5 MRO 會是 (D, B, A, C)。結果是 **A** 出現在 **C** 之前,即使 **C** 是 **A** 的子類別。然而,C3 MRO 演算法會產生下列順序:(D, B, C, A),沒有這個問題。

這個範例相當簡單;對於更複雜的案例和更深入的說明,請參閱「請參閱」區段中的連結。

函式

mro::get_linear_isa($classname[, $type])

傳回一個陣列參考,它是給定類別的線性化 MRO。預設使用目前對該類別有效的任何 MRO,或給定的 MRO(如果指定為 $type,則為 c3dfs)。

類別的線性化 MRO 是所有類別的有序陣列,當解析該類別上的方法時會搜尋這些類別,從類別本身開始。

如果請求的類別尚未存在,這個函式仍會成功,並傳回 [ $classname ]

請注意,UNIVERSAL(以及 UNIVERSAL 的 MRO 的任何成員)不屬於類別的 MRO,即使所有類別都隱含繼承自 UNIVERSAL 及其父項的方法。

mro::set_mro ($classname, $type)

將給定類別的 MRO 設定為 $type 參數(c3dfs)。

mro::get_mro($classname)

傳回指定類別的 MRO(可能是 c3dfs)。

mro::get_isarev($classname)

取得此類別的 mro_isarev,傳回類別名稱的陣列參考。這些是每個「isa」指定類別名稱的類別,即使 isa 關係是間接的。MRO 程式碼會在內部使用此功能來追蹤方法/MRO 快取無效化。

與上述的 mro::get_linear_isa 一樣,UNIVERSAL 是特殊的。UNIVERSAL(及其父類別)的 isarev 清單不包含現存的每個類別,即使所有類別在方法繼承的用途上都是後代。

mro::is_universal($classname)

傳回布林狀態,指出指定的類別名稱是否為 UNIVERSAL 本身,或透過 @ISA 繼承而成為 UNIVERSAL 的父類別之一。

此函數傳回 true 的任何類別在意義上都是「通用的」,因為所有類別都可能從中繼承方法。

mro::invalidate_all_method_caches()

遞增 PL_sub_generation,這會使所有套件中的方法快取無效化。

mro::method_changed_in($classname)

使任何依賴於指定類別的類別的方法快取無效化。這通常不需要。已知的唯一情況是純 perl 程式碼會混淆方法快取,就是當您手動安裝新的常數子程式,使用唯讀純量值,例如 constant 的內部會這樣做。如果您找到其他情況,請回報給我們,讓我們可以修正或在此處記載例外。

mro::get_pkg_gen($classname)

傳回一個整數,每當套件 $classname 中的真實區域方法變更,或 $classname 的區域 @ISA 被修改時,此整數就會遞增。

這適用於執行大量類別內省的模組作者,因為這允許他們在上次查看後,快速檢查指定類別的區域屬性是否有任何重要變更。它不會在超類別中的方法/@ISA 變更時遞增。

找出實際變更仍由您負責,而且實際上可能沒有任何變更。也許自您上次檢查後的所有變更都相互抵銷,讓套件處於與之前相同的狀態。

當套件快取被實例化時,此整數通常從 1 開始。在快取根本不存在的套件上呼叫它會傳回 0。如果套件快取被完全刪除(這並非正常情況,但如果有人執行類似 undef %PkgName:: 的動作,就會發生),數字會重設為 01,視套件被清除的完整程度而定。

next::method

這有點像 SUPER,但它使用 C3 方法解析順序,以便在多重繼承情況下獲得更好的相容性。請注意,雖然繼承通常遵循給定類別中有效的 MRO,但 next::method 只使用 C3 MRO。

一般使用方式如下

sub some_method {
  my $self = shift;
  my $superclass_answer = $self->next::method(@_);
  return $superclass_answer + 1;
}

請注意,您不會(重新)指定方法名稱。它強制您始終使用與您開始的方法相同的方法名稱。

當然,它可以被呼叫在物件或類別上。

它解析要呼叫哪個實際方法的方式是

  1. 首先,它確定它被呼叫的物件或類別的線性化 C3 MRO。

  2. 然後,它確定它被呼叫的上下文的類別和方法名稱。

  3. 最後,它沿著 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::can

這類似於 next::method,但只會傳回程式碼參考或 undef 以指示不存在此名稱的進一步方法。

maybe::next::method

在簡單的情況下,它等於

$self->next::method(@_) if $self->next::can;

但有些情況下只有這個解決方案有效(例如 goto &maybe::next::method);

另請參閱

原始 Dylan 文件

http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.19.3910&rep=rep1&type=pdf

Python 2.3 MRO

https://www.python.org/download/releases/2.3/mro/

Class::C3

Class::C3

作者

Brandon L. Black,<blblack@gmail.com>

基於 Stevan Little 的 Class::C3