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']);
當繼承一個套件時,此模組允許該套件的類別為特定屬性定義屬性處理常式子常式。隨後在該套件中定義的變數和子常式,或從該套件衍生的套件中定義的變數和子常式,可以賦予與屬性處理常式子常式名稱相同的屬性,然後將在編譯階段之一(即在 BEGIN
、CHECK
、INIT
或 END
區塊中)呼叫這些屬性處理常式子常式。(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 {...}
都會呼叫上述處理常式,並傳遞
宣告此子常式的套件名稱;
包含此子常式的符號表項目 (typeglob) 參考;
此子常式的參考;
屬性的名稱;
與該屬性關聯的任何資料;
呼叫處理常式的階段名稱;
呼叫處理常式的檔案名稱;
此檔案中的行號。
同樣地,在套件中宣告任何具有 :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
規格說明內建類型(SCALAR
、ARRAY
、HASH
或 CODE
)的名稱,則處理常式只套用於該類型的宣告。例如,下列定義
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;
如果在 "SYNOPSIS" 中顯示的類別被放置在 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()
。
my $symbol = Attribute::Handlers::findsym($package, $referent);
此函式在 $package
的符號表中尋找 $referent
的 typeglob,它是一個變數或子常式的參考 (SCALAR、ARRAY、HASH 或 CODE)。如果它找到 typeglob,它會傳回它。否則,它會傳回 undef。請注意,findsym
會記住它先前成功找到的 typeglob,因此使用相同引數的後續呼叫應該會快很多。
錯誤的屬性類型:ATTR(%s)
屬性處理常式已使用 :ATTR(ref_type)
指定,但它定義為處理的參考類型不是五種許可類型之一:SCALAR
、ARRAY
、HASH
、CODE
或 ANY
。
屬性處理常式 %s 不處理 %s 屬性
已定義指定名稱 的屬性處理常式,但未定義為處理指定類型的宣告。通常在嘗試將 VAR
屬性處理常式套用至子常式,或將 SCALAR
屬性處理常式套用至其他類型的變數時會遇到。
在套件 %s 中宣告 %s 屬性可能會與未來的保留字衝突
已宣告一個名稱全部為小寫的屬性處理常式。名稱全部為小寫的屬性可能在某天對 Perl 本身具有意義,即使目前大多數沒有。請改用混合大小寫的屬性名稱。
一個子常式不能有兩個 ATTR 規格說明
你就是不能,好嗎?請改為將所有規格說明放在一起,並在單一 ATTR(specification)
中用逗號分隔它們。
無法自動繫結 %s
您只能為 "SCALAR"
、"ARRAY"
和 "HASH"
類型宣告自動化。它們是 Perl 可以繫結的唯一項目(除了無法宣告的類型 glob)。
內部錯誤:%s 符號遺失
程式狀態有誤。一個帶有屬性的子常式在宣告和呼叫其屬性處理常式之間消失。
無法套用 END 處理常式
您已為套用至詞彙變數的屬性定義 END 處理常式。由於變數在 END 期間可能不可用,因此不會執行此操作。
Damian Conway (damian@conway.org)。此模組的維護者現為 Rafael Garcia-Suarez (rgarciasuarez@gmail.com)。
CPAN 發行的維護者為 Steffen Mueller (smueller@cpan.org)。如有 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.