Что "лучший" путь состоит в том, чтобы использовать "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);
Первое использование eval
, второе использование B::
(по крайней мере, для разновидности non-XS Скаляра:: Util).
Первое, кажется, не работает правильно если $x
скаляр, содержащий имя класса, как проиллюстрировано ниже, таким образом, я склоняюсь к № 2 (использование blessed
) если somoene не указывает на серьезное основание не к.
$ perl5.8 -e '{use IO::Handle;$x="IO::Handle";
eval {$is = $x->isa("IO::Handle")}; print "$is:$@\n";}'
1:
Там какие-либо объективные причины состоят в том, чтобы выбрать один из этих двух подходов (или 3-й, о котором я не знаю), такие как производительность, не обрабатывая некоторый особый случай, и т.д....?
Реализация Scalar :: Util
категорически лучше. Это позволяет избежать накладных расходов на eval {}
, которые всегда приводят к установке дополнительной переменной.
perl -we'$@=q[foo]; eval {}; print $@'
Реализация Scalar :: Util
легче читается (она не умирает по причине, неизвестной в коде). Если eval тоже терпит неудачу, я считаю, что происходит то, что вы идете в обратном направлении по дереву до состояния, предшествовавшего eval - так достигается состояние сброса. Это приводит к дополнительным накладным расходам в случае сбоя.
Совсем не объект
Rate eval su
eval 256410/s -- -88%
su 2222222/s 767% --
Объект, прошедший isa check
Rate su eval
su 1030928/s -- -16%
eval 1234568/s 20% --
Объект не прошел проверку, isa
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' ) }
}
);
Я использовал Это perl, v5.10.1 (*), созданный для i486 -linux-gnu-thread-multi и Scalar :: Util
, 1.21
Это может показаться немного резким для Perl, но ни один из них не идеален. Оба прикрывают тот факт, что объекты - это привязка к Perl. Благословенная идиома
многословна и содержит более пары простых частей.
blessed( $object ) && object->isa( 'Class' )
Я бы предпочел что-то более похожее на это:
object_isa( $object, 'Class' )
Нет логической операции, в которой можно было бы ошибиться, и большинство неподходящих применений будет исключено компилятором. (Цитаты не закрыты, без запятой, скобки не закрыты, вместо этого вызывается object_isa
...)
Потребуются неопределенные скаляры, простые скаляры (если только они не являются именем класса, которое является Class
), неблагословенные ссылки и благословенные ссылки, которые не расширяют 'Class' и говорят вам, что нет, они не являются объектами Class
. Если мы не хотим идти по пути autobox
-ing все, нам понадобится функция , которая просто сообщает нам.
Возможно, существует третий параметр для $ how_close
, но также может быть что-то вроде этого:
if ( my $ranking = object_isa( $object, 'Class' )) {
...
}
else {
given ( $ranking ) {
when ( NOT_TYPE ) { ... }
when ( NOT_BLESSED ) { ... }
when ( NOT_REF ) { ... }
when ( NOT_DEFINED ) { ... }
}
}
Я вижу, что это единственный способ вернуть такое количество unique ложно, если $ rank
был благословлен в классе, который перегружал логический оператор для возврата false, если только функция не вернула одно значение, указывающее на связь ISA.
Тем не менее, он может иметь несколько членов: ТОЧНО
, НАСЛЕДОВАНИЕ
, РЕАЛИЗАЦИИ
, АГРЕГАТЫ
или даже MOCKS
Мне тоже надоело набирать это:
$object->can( 'DOES' ) && $object->DOES( 'role' )
, потому что я пытаюсь реализовать обращенные к будущему DOES в меньших perl'ах (из-за идеи, что люди могут осуждать мое загрязнение UNIVERSAL
) на них.
Вы можете обернуть проверки безопасности в скаляр и затем использовать скаляр как метод, чтобы сохранить чистоту:
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"}
}
}
this prints:
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.