使用"isa"方法的最佳方法是什么?

DVK*_*DVK 14 perl isa

什么是可靠地使用"isa()"的"最佳"方式?换句话说,它可以在任何值上正确工作,而不仅仅是一个对象.

"最佳",我的意思是缺乏未处理的角落案例以及缺乏潜在的性能问题,因此这不是一个主观问题.

这个问题提到了两种似乎可靠的方法(请注意,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);
Run Code Online (Sandbox Code Playgroud)

第一个使用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:
Run Code Online (Sandbox Code Playgroud)

有没有客观的理由选择这两种方法中的一种(或者我不知道的第三种方法),如性能,不处理某些特殊情况等等?

Eva*_*oll 13

Scalar::Util实施是断然更好.它避免了eval {}总是导致设置附加变量的开销.

perl -we'$@=q[foo]; eval {}; print $@'
Run Code Online (Sandbox Code Playgroud)

Scalar::Util实现更容易阅读(它不会为一个原因,是未知的代码死亡).如果eval也失败了,我相信会发生的事情是你在树中向后走到eval之前的状态 - 这就是重置状态的实现方式.这会带来额外的故障开销.

基准

根本不是一个对象

          Rate eval   su
eval  256410/s   -- -88%
su   2222222/s 767%   --
Run Code Online (Sandbox Code Playgroud)

对象传递是检查

          Rate   su eval
su   1030928/s   -- -16%
eval 1234568/s  20%   --
Run Code Online (Sandbox Code Playgroud)

对象失败是检查

         Rate   su eval
su   826446/s   --  -9%
eval 909091/s  10%   --
Run Code Online (Sandbox Code Playgroud)

测试代码:

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' ) }
    }
);
Run Code Online (Sandbox Code Playgroud)

我用这是perl的,v5.10.1(*)为1486-Linux的GNU线程多建,并Scalar::Util,1.21


Eri*_*rom 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" }
Run Code Online (Sandbox Code Playgroud)

请注意,这$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(@_)};
Run Code Online (Sandbox Code Playgroud)

通过将代码引用放在标量中,可以在没有错误的情况下调用它:

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"}
    }
}
Run Code Online (Sandbox Code Playgroud)

这打印:

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
Run Code Online (Sandbox Code Playgroud)

如果有人认为这实际上有用,请告诉我,我会把它放在CPAN上.


Axe*_*man 5

对Perl来说这听起来有点刺耳,但这些都不是理想的.两者都掩盖了这样一个事实,即对象是Perl的一个方面.这个blessed成语很冗长,包含了几个简单的部分.

blessed( $object ) && object->isa( 'Class' )
Run Code Online (Sandbox Code Playgroud)

我更喜欢这样的东西:

object_isa( $object, 'Class' )
Run Code Online (Sandbox Code Playgroud)

没有逻辑操作可以出错,大多数不合适的用法都会被编译器淘汰.(行情没有结束,没有逗号,parens没有关闭,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 ) { ... }
   }
}
Run Code Online (Sandbox Code Playgroud)

关于我可以看到我们可以返回这个许多独特的谬误的唯一方法是,如果$ranking被赋予了一个重载布尔运算符以返回false的类,除非函数返回指示ISA关系的一个值.

但是,它可能有几个成员:EXACTLY,INHERITS,"使IMPLEMENTS骨料AGGREGATESMOCKS`

我也厌倦了输入这个:

$object->can( 'DOES' ) && $object->DOES( 'role' )
Run Code Online (Sandbox Code Playgroud)

因为我试图在较小的perls中实现面向未来的DOES(人们可能对我的污染不屑一顾MOCKS).


bri*_*foy 5

下面是2020年的Perl v5.32有一个更新isa运营商,也被称为类缀操作符。它处理左侧参数不是对象的情况,它返回 false 而不是炸毁:

use v5.32;
if( $something isa 'Animal' ) { ... }
Run Code Online (Sandbox Code Playgroud)