可靠地使用“isa()”的“最佳”方法是什么?换句话说,它可以在任何值上正常工作,而不仅仅是一个对象。
“最佳”,我的意思是缺乏未处理的角落案例以及缺乏潜在的性能问题,因此这不是一个主观问题。
This question提到了两种似乎可靠的方法(请注意,不应使用旧格式UNIVERSAL::isa()
,并在Q的答案中有充分记录的原因):
eval { $x->isa("Class") }
#and check $@ in case $x was not an object, in case $x was not an object
use Scalar::Util 'blessed';
blessed $x && $x ->isa($class);
第一个使用eval
,第二个使用B::
(至少对于Scalar :: Util的非XS风格)。
如果$x
是包含类名的标量,第一个似乎无法正常工作,如下图所示,所以我倾向于#2(使用blessed
),除非somoene表示好理由不去。
$ perl5.8 -e '{use IO::Handle;$x="IO::Handle";
eval {$is = $x->isa("IO::Handle")}; print "$is:$@\n";}'
1:
有没有客观的理由选择这两种方法中的一种(或者我不知道的第三种方法),例如性能,不处理某些特殊情况等等? < / p>
答案 0 :(得分:13)
Scalar::Util
实施明显更好。它避免了eval {}
的开销,这总是导致设置一个额外的变量。
perl -we'$@=q[foo]; eval {}; print $@'
Scalar::Util
实现更容易阅读(它不会因代码未知的原因而死亡)。如果eval也失败了,我相信会发生的事情是你在树中向后走到eval之前的状态 - 这就是重置状态的实现方式。这会带来额外的失败开销。
根本不是对象
Rate eval su
eval 256410/s -- -88%
su 2222222/s 767% --
对象传递是检查
Rate su eval
su 1030928/s -- -16%
eval 1234568/s 20% --
对象失败是检查
Rate su eval
su 826446/s -- -9%
eval 909091/s 10% --
测试代码:
use strict;
use warnings;
use Benchmark;
use Scalar::Util;
package Foo;
Benchmark::cmpthese(
1_000_000
, {
eval => sub{ eval{ $a->isa(__PACKAGE__) } }
, su => sub { Scalar::Util::blessed $a && $a->isa(__PACKAGE__) }
}
);
package Bar;
$a = bless {};
Benchmark::cmpthese(
1_000_000
, {
eval => sub{ eval{ $a->isa(__PACKAGE__)} }
, su => sub { Scalar::Util::blessed $a && $a->isa(__PACKAGE__) }
}
);
package Baz;
$a = bless {};
Benchmark::cmpthese(
1_000_000
, {
eval => sub{ eval{ $a->isa('duck')} }
, su => sub { Scalar::Util::blessed $a && $a->isa( 'duck' ) }
}
);
我用过这是为i486-linux-gnu-thread-multi和Scalar::Util
,1.21
答案 1 :(得分:6)
您可以将安全检查包装在标量中,然后使用标量作为保持清洁的方法:
use Scalar::Util 'blessed';
my $isa = sub {blessed $_[0] and $_[0]->isa($_[1])};
my $obj;
if ($obj->$isa('object')) { ... } # returns false instead of throwing an error
$obj = {};
if ($obj->$isa('object')) { ... } # returns false as well
bless $obj => 'object';
if ($obj->$isa('object')) { say "we got an object" }
请注意,$obj->$isa(...)
只是$isa->($obj, ...)
的不同拼写,因此实际上不会发生任何方法调用(这就是为什么它可以避免抛出任何错误)。
这里有一些代码可以让你在任何事情上调用isa
,然后检查结果(灵感来自Axeman的回答):
{package ISA::Helper;
use Scalar::Util;
sub new {
my ($class, $obj, $type) = @_;
my $blessed = Scalar::Util::blessed $obj;
bless {
type => $type,
obj => $obj,
blessed => $blessed,
isa => $blessed && $obj->isa($type)
} => $class
}
sub blessed {$_[0]{blessed}}
sub type {$_[0]{isa}}
sub ref {ref $_[0]{obj}}
sub defined {defined $_[0]{obj}}
use overload fallback => 1,
bool => sub {$_[0]{isa}};
sub explain {
my $self = shift;
$self->type ? "object is a $$self{type}" :
$self->blessed ? "object is a $$self{blessed} not a $$self{type}" :
$self->ref ? "object is a reference, but is not blessed" :
$self->defined ? "object is defined, but not a reference"
: "object is not defined"
}
}
my $isa = sub {ISA::Helper->new(@_)};
通过将代码引用放在标量中,可以在没有错误的情况下调用它:
my @items = (
undef,
5,
'five',
\'ref',
bless( {} => 'Other::Pkg'),
bless( {} => 'My::Obj'),
);
for (@items) {
if (my $ok = $_->$isa('My::Obj')) {
print 'ok: ', $ok->explain, "\n";
} else {
print 'error: ', $ok->explain, "\n";
}
}
print undef->$isa('anything?')->explain, "\n";
my $obj = bless {} => 'Obj';
print $obj->$isa('Obj'), "\n";
my $ref = {};
if (my $reason = $ref->$isa('Object')) {
say "all is well"
} else {
given ($reason) {
when (not $_->defined) {say "not defined"}
when (not $_->ref) {say "not a reference"}
when (not $_->blessed) {say "not a blessed reference"}
when (not $_->type) {say "not correct type"}
}
}
打印:
error: object is not defined
error: object is defined, but not a reference
error: object is defined, but not a reference
error: object is a reference, but is not blessed
error: object is a Other::Pkg not a My::Obj
ok: object is a My::Obj
object is not defined
1
not a blessed reference
如果有人认为这实际上有用,请告诉我,我会把它放在CPAN上。
答案 2 :(得分:5)
这对Perl来说听起来有点刺耳,但这些都不是理想的。两者都掩盖了这样一个事实,即对象是Perl的一个方面。 blessed
成语是罗嗦的,包含多个简单的部分。
blessed( $object ) && object->isa( 'Class' )
我更喜欢这样的东西:
object_isa( $object, 'Class' )
没有逻辑操作可以出错,大多数不合适的用法都会被编译器淘汰。 (行情没有结束,没有逗号,没有关闭,调用object_isa
而不是......)
它将采用未定义的标量,简单的标量(除非它们是是 Class
的类名),未经证实的引用,以及不扩展“类”的祝福引用并告诉您不,它们不是Class
个对象。除非我们想要走autobox
所有的路线,否则我们需要一个简单地告诉我们的功能。
也许$how_close
可能有第三个参数,但也可能是这样的:
if ( my $ranking = object_isa( $object, 'Class' )) {
...
}
else {
given ( $ranking ) {
when ( NOT_TYPE ) { ... }
when ( NOT_BLESSED ) { ... }
when ( NOT_REF ) { ... }
when ( NOT_DEFINED ) { ... }
}
}
关于我可以看到我们可以返回这么多唯一 falses的唯一方法是,如果$ranking
被祝福到一个重载了布尔运算符的类以返回false,除非函数返回了一个值表示ISA关系。
但是,它可能只有少数成员:EXACTLY
,INHERITS
,IMPLEMENTS
,AGGREGATES
或甚至MOCKS
我也厌倦了打字:
$object->can( 'DOES' ) && $object->DOES( 'role' )
因为我试图在较小的perls中实现面向未来的DOES(关于人们可能对我的污染UNIVERSAL
皱眉的想法)。
答案 3 :(得分:5)
这是 2020 年的更新。Perl v5.32 具有 the isa
operator,也称为类中缀运算符。它处理左侧参数不是对象的情况,它返回 false 而不是炸毁:
use v5.32;
if( $something isa 'Animal' ) { ... }