内容

名称

Attribute::Handlers - 更简单的属性处理程序定义

版本

本文档描述了 Attribute::Handlers 的 1.03 版本。

概要

    package MyClass;
    require 5.006;
    use Attribute::Handlers;
    no warnings 'redefine';


    sub Good : ATTR(SCALAR) {
	my ($package, $symbol, $referent, $attr, $data) = @_;

	# Invoked for any scalar variable with a :Good attribute,
	# provided the variable was declared in MyClass (or
	# a derived class) or typed to MyClass.

	# Do whatever to $referent here (executed in CHECK phase).
	...
    }

    sub Bad : ATTR(SCALAR) {
	# Invoked for any scalar variable with a :Bad attribute,
	# provided the variable was declared in MyClass (or
	# a derived class) or typed to MyClass.
	...
    }

    sub Good : ATTR(ARRAY) {
	# Invoked for any array variable with a :Good attribute,
	# provided the variable was declared in MyClass (or
	# a derived class) or typed to MyClass.
	...
    }

    sub Good : ATTR(HASH) {
	# Invoked for any hash variable with a :Good attribute,
	# provided the variable was declared in MyClass (or
	# a derived class) or typed to MyClass.
	...
    }

    sub Ugly : ATTR(CODE) {
	# Invoked for any subroutine declared in MyClass (or a 
	# derived class) with an :Ugly attribute.
	...
    }

    sub Omni : ATTR {
	# Invoked for any scalar, array, hash, or subroutine
	# with an :Omni attribute, provided the variable or
	# subroutine was declared in MyClass (or a derived class)
	# or the variable was typed to MyClass.
	# Use ref($_[2]) to determine what kind of referent it was.
	...
    }


    use Attribute::Handlers autotie => { Cycle => Tie::Cycle };

    my $next : Cycle(['A'..'Z']);

描述

当这个模块被一个包继承时,它允许该包的类为特定属性定义属性处理程序子例程。随后在该包中或从该包派生的包中定义的变量和子例程可以被赋予与属性处理程序子例程相同的名称的属性,这些子例程将在编译阶段之一(即在 BEGINCHECKINITEND 块中)被调用。(UNITCHECK 块不对应于全局编译阶段,因此不能在此指定。)

要创建处理程序,请将其定义为与所需属性同名的子例程,并使用属性 :ATTR 声明该子例程本身。例如

    package LoudDecl;
    use Attribute::Handlers;

    sub Loud :ATTR {
	my ($package, $symbol, $referent, $attr, $data, $phase,
	    $filename, $linenum) = @_;
	print STDERR
	    ref($referent), " ",
	    *{$symbol}{NAME}, " ",
	    "($referent) ", "was just declared ",
	    "and ascribed the ${attr} attribute ",
	    "with data ($data)\n",
	    "in phase $phase\n",
	    "in file $filename at line $linenum\n";
    }

这在 LoudDecl 类中创建了一个名为 :Loud 的属性的处理程序。此后,在 LoudDecl 类中声明具有 :Loud 属性的任何子例程

package LoudDecl;

sub foo: Loud {...}

会导致上述处理程序被调用,并传递

[0]

声明该子程序所在的包的名称;

[1]

对包含该子程序的符号表条目(类型全局变量)的引用;

[2]

对该子程序的引用;

[3]

属性的名称;

[4]

与该属性关联的任何数据;

[5]

调用处理程序的阶段名称;

[6]

调用处理程序的文件名;

[7]

该文件中的行号。

同样,在包中声明任何带有 :Loud 属性的变量

package LoudDecl;

my $foo :Loud;
my @foo :Loud;
my %foo :Loud;

将导致处理程序被调用,并使用类似的参数列表(当然,$_[2] 将是对该变量的引用)。

包名称参数通常是声明子程序的类的名称,但也可能是派生类的名称(因为处理程序是继承的)。

如果一个词法变量被赋予一个属性,那么它就不属于任何符号表,因此在这种情况下,符号表参数($_[1])被设置为字符串 'LEXICAL'。同样,将属性赋予匿名子程序会导致符号表参数为 'ANON'

数据参数传递与属性关联的值(如果有)。例如,如果 &foo 被声明为

sub foo :Loud("turn it up to 11, man!") {...}

那么对包含字符串 "turn it up to 11, man!" 的数组的引用将作为最后一个参数传递。

Attribute::Handlers 尽力将数据参数($_[4])转换为可用的形式,然后再将其传递给处理程序(但请参阅 "非解释性属性处理程序")。如果这些努力成功,则解释后的数据将以数组引用的形式传递;如果失败,则原始数据将以字符串的形式传递。例如,所有这些

sub foo :Loud(till=>ears=>are=>bleeding) {...}
sub foo :Loud(qw/till ears are bleeding/) {...}
sub foo :Loud(qw/till, ears, are, bleeding/) {...}
sub foo :Loud(till,ears,are,bleeding) {...}

导致它将 ['till','ears','are','bleeding'] 作为处理程序的数据参数传递。而

sub foo :Loud(['till','ears','are','bleeding']) {...}

导致它传递 [ ['till','ears','are','bleeding'] ];在数据中指定的数组引用被传递到标准数组引用中,表示成功解释。

但是,如果数据无法解析为有效的 Perl,则它将作为未解释的字符串传递。例如

sub foo :Loud(my,ears,are,bleeding) {...}
sub foo :Loud(qw/my ears are bleeding) {...}

导致字符串 'my,ears,are,bleeding''qw/my ears are bleeding' 分别作为数据参数传递。

如果没有与属性关联的值,则传递 undef

类型化词法变量

无论词法变量是在哪个包中声明的,如果它被赋予了一个属性,那么被调用的处理程序将是属于它被类型化的包的处理程序。例如,以下声明

package OtherClass;

my LoudDecl $loudobj : Loud;
my LoudDecl @loudobjs : Loud;
my LoudDecl %loudobjex : Loud;

将导致 LoudDecl::Loud 处理程序被调用(即使 OtherClass 也为 :Loud 属性定义了处理程序)。

特定类型的属性处理程序

如果声明了一个属性处理程序,并且 :ATTR 说明符被赋予了一个内置类型的名称(SCALARARRAYHASHCODE),那么该处理程序只应用于该类型的声明。例如,以下定义

package LoudDecl;

sub RealLoud :ATTR(SCALAR) { print "Yeeeeow!" }

创建一个仅应用于标量的属性处理程序

package Painful;
use base LoudDecl;

my $metal : RealLoud;           # invokes &LoudDecl::RealLoud
my @metal : RealLoud;           # error: unknown attribute
my %metal : RealLoud;           # error: unknown attribute
sub metal : RealLoud {...}      # error: unknown attribute

当然,你也可以为这些类型分别声明处理程序(但你需要指定 no warnings 'redefine' 来静默地执行此操作)

package LoudDecl;
use Attribute::Handlers;
no warnings 'redefine';

sub RealLoud :ATTR(SCALAR) { print "Yeeeeow!" }
sub RealLoud :ATTR(ARRAY) { print "Urrrrrrrrrr!" }
sub RealLoud :ATTR(HASH) { print "Arrrrrgggghhhhhh!" }
sub RealLoud :ATTR(CODE) { croak "Real loud sub torpedoed" }

你也可以明确地指示一个处理程序用于所有类型的引用,如下所示

package LoudDecl;
use Attribute::Handlers;

sub SeriousLoud :ATTR(ANY) { warn "Hearing loss imminent" }

(即 ATTR(ANY):ATTR 的同义词)。

非解释性属性处理程序

有时,Attribute::Handlers 为将数据参数($_[4])转换为可用的形式并在传递给处理程序之前所做的努力会妨碍操作。

你可以通过使用关键字 RAWDATA 声明一个属性处理程序来关闭这种热心帮助。例如

sub Raw          : ATTR(RAWDATA) {...}
sub Nekkid       : ATTR(SCALAR,RAWDATA) {...}
sub Au::Naturale : ATTR(RAWDATA,ANY) {...}

然后,处理程序绝对不会尝试解释它接收到的数据,而只是将其作为字符串传递

my $power : Raw(1..100);        # handlers receives "1..100"

特定阶段的属性处理程序

默认情况下,属性处理程序在编译阶段的末尾(在 CHECK 块中)被调用。这在大多数情况下似乎是最佳的,因为到那时可以定义的大多数东西都已经定义了,但还没有执行任何东西。

但是,可以通过明确地指定希望属性处理程序被调用的阶段(或阶段),来设置在程序的编译或执行中的其他点被调用的属性处理程序。例如

sub Early    :ATTR(SCALAR,BEGIN) {...}
sub Normal   :ATTR(SCALAR,CHECK) {...}
sub Late     :ATTR(SCALAR,INIT) {...}
sub Final    :ATTR(SCALAR,END) {...}
sub Bookends :ATTR(SCALAR,BEGIN,END) {...}

如最后一个示例所示,可以设置一个处理程序,使其在两个或多个阶段被(重新)调用。阶段名称作为处理程序的最后一个参数传递。

请注意,为 BEGIN 阶段安排的属性处理程序会在检测到属性时立即处理(即在执行任何后续定义的 BEGIN 块之前)。

属性作为 tie 接口

属性提供了一种优秀且直观的接口来绑定变量。例如

    use Attribute::Handlers;
    use Tie::Cycle;

    sub UNIVERSAL::Cycle : ATTR(SCALAR) {
	my ($package, $symbol, $referent, $attr, $data, $phase) = @_;
	$data = [ $data ] unless ref $data eq 'ARRAY';
	tie $$referent, 'Tie::Cycle', $data;
    }

    # and thereafter...

    package main;

    my $next : Cycle('A'..'Z');     # $next is now a tied variable

    while (<>) {
	print $next;
    }

请注意,由于Cycle属性在$data变量中接收其参数,如果该属性被赋予一个参数列表,$data将包含一个单一的数组引用;否则,它将直接包含单个参数。由于Tie::Cycle要求其循环值作为数组引用传递,这意味着我们需要将非数组引用参数包装在数组构造器中

$data = [ $data ] unless ref $data eq 'ARRAY';

然而,通常情况下,情况正好相反:可绑定类期望其参数为扁平化的列表,因此属性看起来像

    sub UNIVERSAL::Cycle : ATTR(SCALAR) {
	my ($package, $symbol, $referent, $attr, $data, $phase) = @_;
	my @data = ref $data eq 'ARRAY' ? @$data : $data;
	tie $$referent, 'Tie::Whatever', @data;
    }

这种软件模式应用范围非常广泛,Attribute::Handlers提供了一种自动化它的方法:在use Attribute::Handlers语句中指定'autotie'。因此,循环示例也可以写成

    use Attribute::Handlers autotie => { Cycle => 'Tie::Cycle' };

    # and thereafter...

    package main;

    my $next : Cycle(['A'..'Z']);     # $next is now a tied variable

    while (<>) {
	print $next;
    }

请注意,我们现在必须将循环值作为数组引用传递,因为autotie机制将tie的参数列表作为列表传递(如Tie::Whatever示例中),而不是作为数组引用(如本节开头原始的Tie::Cycle示例)。

'autotie'后面的参数是一个哈希引用,其中每个键都是要创建的属性的名称,每个值都是将该属性分配给的变量应该绑定的类。

请注意,不再需要导入Tie::Cycle模块——Attribute::Handlers会自动处理。您甚至可以通过将它们附加到类名来将参数传递给模块的import子例程。例如

    use Attribute::Handlers
	 autotie => { Dir => 'Tie::Dir qw(DIR_UNLINK)' };

如果属性名未限定,则属性将安装在当前包中。否则,它将安装在限定符的包中

package Here;

use Attribute::Handlers autotie => {
     Other::Good => Tie::SecureHash, # tie attr installed in Other::
             Bad => Tie::Taxes,      # tie attr installed in Here::
 UNIVERSAL::Ugly => Software::Patent # tie attr installed everywhere
};

自动绑定最常用于它们实际绑定的模块,并且需要将其属性导出到调用它们的任何模块。为了方便起见,Attribute::Handlers识别一个特殊的“伪类”——__CALLER__,它可以作为属性的限定符指定

    package Tie::Me::Kangaroo::Down::Sport;

    use Attribute::Handlers autotie =>
	 { '__CALLER__::Roo' => __PACKAGE__ };

这会导致Attribute::Handlers在导入Tie::Me::Kangaroo::Down::Sport模块的包中定义Roo属性。

请注意,对__CALLER__::Roo标识符进行引号非常重要,因为perl 5.8中的一个错误将拒绝解析它并导致未知错误。

将绑定对象传递给tie

有时,将对绑定到 TIESCALAR、TIEHASH 等的对象的引用传递给绑定它的对象非常重要。

autotie 机制也支持这一点。以下代码

use Attribute::Handlers autotieref => { Selfish => Tie::Selfish };
my $var : Selfish(@args);

与以下代码的效果相同

tie my $var, 'Tie::Selfish', @args;

但是,当使用 "autotieref" 而不是 "autotie"

use Attribute::Handlers autotieref => { Selfish => Tie::Selfish };
my $var : Selfish(@args);

其效果是将对正在绑定的变量的额外引用传递给 tie 调用。

tie my $var, 'Tie::Selfish', \$var, @args;

示例

如果在 MyClass.pm 模块中放置了 "概要" 中所示的类,则以下代码

package main;
use MyClass;

my MyClass $slr :Good :Bad(1**1-1) :Omni(-vorous);

package SomeOtherClass;
use base MyClass;

sub tent { 'acle' }

sub fn :Ugly(sister) :Omni('po',tent()) {...}
my @arr :Good :Omni(s/cie/nt/);
my %hsh :Good(q/bye/) :Omni(q/bus/);

将导致以下处理程序被调用

# my MyClass $slr :Good :Bad(1**1-1) :Omni(-vorous);

MyClass::Good:ATTR(SCALAR)( 'MyClass',          # class
                            'LEXICAL',          # no typeglob
                            \$slr,              # referent
                            'Good',             # attr name
                            undef               # no attr data
                            'CHECK',            # compiler phase
                          );

MyClass::Bad:ATTR(SCALAR)( 'MyClass',           # class
                           'LEXICAL',           # no typeglob
                           \$slr,               # referent
                           'Bad',               # attr name
                           0                    # eval'd attr data
                           'CHECK',             # compiler phase
                         );

MyClass::Omni:ATTR(SCALAR)( 'MyClass',          # class
                            'LEXICAL',          # no typeglob
                            \$slr,              # referent
                            'Omni',             # attr name
                            '-vorous'           # eval'd attr data
                            'CHECK',            # compiler phase
                          );


# sub fn :Ugly(sister) :Omni('po',tent()) {...}

MyClass::UGLY:ATTR(CODE)( 'SomeOtherClass',     # class
                          \*SomeOtherClass::fn, # typeglob
                          \&SomeOtherClass::fn, # referent
                          'Ugly',               # attr name
                          'sister'              # eval'd attr data
                          'CHECK',              # compiler phase
                        );

MyClass::Omni:ATTR(CODE)( 'SomeOtherClass',     # class
                          \*SomeOtherClass::fn, # typeglob
                          \&SomeOtherClass::fn, # referent
                          'Omni',               # attr name
                          ['po','acle']         # eval'd attr data
                          'CHECK',              # compiler phase
                        );


# my @arr :Good :Omni(s/cie/nt/);

MyClass::Good:ATTR(ARRAY)( 'SomeOtherClass',    # class
                           'LEXICAL',           # no typeglob
                           \@arr,               # referent
                           'Good',              # attr name
                           undef                # no attr data
                           'CHECK',             # compiler phase
                         );

MyClass::Omni:ATTR(ARRAY)( 'SomeOtherClass',    # class
                           'LEXICAL',           # no typeglob
                           \@arr,               # referent
                           'Omni',              # attr name
                           ""                   # eval'd attr data 
                           'CHECK',             # compiler phase
                         );


# my %hsh :Good(q/bye) :Omni(q/bus/);

MyClass::Good:ATTR(HASH)( 'SomeOtherClass',     # class
                          'LEXICAL',            # no typeglob
                          \%hsh,                # referent
                          'Good',               # attr name
                          'q/bye'               # raw attr data
                          'CHECK',              # compiler phase
                        );

MyClass::Omni:ATTR(HASH)( 'SomeOtherClass',     # class
                          'LEXICAL',            # no typeglob
                          \%hsh,                # referent
                          'Omni',               # attr name
                          'bus'                 # eval'd attr data
                          'CHECK',              # compiler phase
                        );

将处理程序安装到 UNIVERSAL 中,使它们...嗯...通用。例如

package Descriptions;
use Attribute::Handlers;

my %name;
sub name { return $name{$_[2]}||*{$_[1]}{NAME} }

sub UNIVERSAL::Name :ATTR {
    $name{$_[2]} = $_[4];
}

sub UNIVERSAL::Purpose :ATTR {
    print STDERR "Purpose of ", &name, " is $_[4]\n";
}

sub UNIVERSAL::Unit :ATTR {
    print STDERR &name, " measured in $_[4]\n";
}

允许您编写

use Descriptions;

my $capacity : Name(capacity)
             : Purpose(to store max storage capacity for files)
             : Unit(Gb);


package Other;

sub foo : Purpose(to foo all data before barring it) { }

# etc.

实用程序函数

此模块提供一个实用程序函数 findsym()

findsym
my $symbol = Attribute::Handlers::findsym($package, $referent);

该函数在 $package 的符号表中查找 $referent 的类型全局变量,$referent 是对变量或子例程(SCALAR、ARRAY、HASH 或 CODE)的引用。如果找到类型全局变量,则返回它。否则,返回 undef。请注意,findsym 会记忆之前成功找到的类型全局变量,因此使用相同参数的后续调用应该快得多。

诊断信息

属性类型错误:ATTR(%s)

属性处理程序使用 :ATTR(ref_type) 指定,但它被定义为处理的引用类型不是五个允许的类型之一:SCALARARRAYHASHCODEANY

属性处理程序 %s 不处理 %s 属性

为指定名称的属性定义了处理程序,但没有为指定的声明类型定义处理程序。通常在尝试将 VAR 属性处理程序应用于子例程或将 SCALAR 属性处理程序应用于其他类型的变量时遇到此错误。

在包 %s 中声明 %s 属性可能会与将来的保留字冲突

声明了对全小写名称的属性的处理程序。将来,即使大多数属性目前还没有,全小写名称的属性也可能对 Perl 本身具有意义。请改用混合大小写的属性名称。

一个子例程不能有两个 ATTR 说明符

你不能这样做,好吗?相反,将所有规范用逗号隔开,放在一个 ATTR(specification) 中。

无法自动绑定 %s

你只能为 "SCALAR""ARRAY""HASH" 类型声明自动绑定。除了类型全局变量(它们不可声明)之外,它们是 Perl 可以绑定的唯一东西。

内部错误:%s 符号丢失

程序的状态有问题。一个带属性的子程序在声明时和其属性处理程序被调用时之间就不存在了。

无法应用 END 处理程序

你为应用于词法变量的属性定义了一个 END 处理程序。由于该变量在 END 期间可能不可用,因此这不会发生。

作者

Damian Conway ([email protected])。该模块的维护者现在是 Rafael Garcia-Suarez ([email protected])。

CPAN 版本的维护者是 Steffen Mueller ([email protected])。有关 CPAN 模块打包方面的技术问题,请联系他。

错误

毫无疑问,在如此奇特的代码中潜伏着严重的错误 :-) 欢迎报告错误和其他反馈。

版权和许可

  Copyright (c) 2001-2014, Damian Conway. All Rights Reserved.
This module is free software. It may be used, redistributed
    and/or modified under the same terms as Perl itself.