内容

名称

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 及更高版本中可用。有关旧版 Perl 的大部分向前兼容实现,请参见 CPAN 上的 MRO::Compat

概述

可以通过使用use mro(如概要中所示)或使用下面的"mro::set_mro"函数来更改给定类的MRO。

在通过userequire加载此mro模块之前,特殊方法next::methodnext::canmaybe::next::method不可用。

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之前,即使CA的子类。然而,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 本身,还是 UNIVERSAL 通过 @ISA 继承的父类之一。

任何此函数返回 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.pythonlang.cn/download/releases/2.3/mro/

Class::C3

Class::C3

作者

Brandon L. Black,<[email protected]>

基于 Stevan Little 的 Class::C3