【发布时间】:2010-12-16 10:10:06
【问题描述】:
如何判断 Perl 变量中的值是什么类型?
$x 可能是标量、数组的引用或哈希的引用(或者可能是其他东西)。
【问题讨论】:
标签: perl
如何判断 Perl 变量中的值是什么类型?
$x 可能是标量、数组的引用或哈希的引用(或者可能是其他东西)。
【问题讨论】:
标签: perl
Perl 提供了
ref()函数,以便您可以在取消引用之前检查引用类型...通过使用
ref()函数,您可以保护取消引用变量的程序代码在使用错误类型的引用时不会产生错误...
【讨论】:
$x 始终是一个标量。提示是符号$:以$ 开头的任何变量(或其他类型的解引用)都是标量。 (有关数据类型的更多信息,请参阅perldoc perldata。)
引用只是一种特定类型的标量。
内置函数ref 会告诉你它是什么类型的引用。另一方面,如果你有一个祝福引用,ref 只会告诉你引用被祝福到的包名称,而不是数据的实际核心类型(祝福引用可以是 hashrefs、arrayrefs 或其他东西)。你可以使用Scalar::Util 的reftype 会告诉你它是什么类型的参考:
use Scalar::Util qw(reftype);
my $x = bless {}, 'My::Foo';
my $y = { };
print "type of x: " . ref($x) . "\n";
print "type of y: " . ref($y) . "\n";
print "base type of x: " . reftype($x) . "\n";
print "base type of y: " . reftype($y) . "\n";
...产生输出:
type of x: My::Foo
type of y: HASH
base type of x: HASH
base type of y: HASH
有关其他类型的引用(例如 coderef、arrayref 等)的更多信息,请参阅此问题:How can I get Perl's ref() function to return REF, IO, and LVALUE? 和 perldoc perlref。
注意:您应该不使用ref 来实现带有祝福对象的代码分支(例如$ref($a) eq "My::Foo" ? say "is a Foo object" : say "foo not defined";)——如果您需要根据变量的类型做出任何决定,使用isa(即if ($a->isa("My::Foo") { ... 或if ($a->can("foo") { ...)。另见polymorphism。
【讨论】:
reftype($x) eq 'HASH' 这样的代码可能会导致警告。 (另一方面,ref 方便地为非引用返回 ''。)
ref 的好用处。
reftype。):) 如果需要直接访问一个受祝福的对象,应该有一个访问器方法来抽象出实现细节。
标量始终包含单个元素。标量变量中的任何内容始终是标量。引用是一个标量值。
如果你想知道它是否是参考,你可以使用ref。如果您想知道引用类型,
您可以使用来自Scalar::Util 的reftype 例程。
如果你想知道它是否是一个对象,你可以使用来自Scalar::Util 的blessed 例程。不过,你永远不应该关心祝福的包裹是什么。 UNIVERSAL 有一些方法可以告诉你一个对象:如果你想检查它是否有你想调用的方法,使用can;如果你想看到它继承自某些东西,请使用isa;如果您想查看对象处理角色,请使用DOES。
如果您想知道该标量是否实际上只是一个标量但与一个类相关联,请尝试tied。如果你得到一个对象,继续你的检查。
如果你想知道它是否看起来像一个数字,你可以使用Scalar::Util中的looks_like_number。如果它看起来不像一个数字并且它不是一个引用,那么它就是一个字符串。但是,所有简单值都可以是字符串。
如果你需要做一些更花哨的事情,你可以使用Params::Validate这样的模块。
【讨论】:
在某个时候,我在 Perlmonks 上读到了一个相当有说服力的论点,即用 ref 或 reftype 测试标量的类型是一个坏主意。我不记得是谁提出了这个想法或链接。对不起。
关键是,在 Perl 中有许多机制可以使给定的标量表现得像你想要的任何东西。如果你tie 一个文件句柄,它就像一个哈希,用reftype 的测试会告诉你你有一个文件句柄。它不会告诉你需要像哈希一样使用它。
所以,论据去了,最好使用鸭子类型来找出变量是什么。
代替:
sub foo {
my $var = shift;
my $type = reftype $var;
my $result;
if( $type eq 'HASH' ) {
$result = $var->{foo};
}
elsif( $type eq 'ARRAY' ) {
$result = $var->[3];
}
else {
$result = 'foo';
}
return $result;
}
你应该这样做:
sub foo {
my $var = shift;
my $type = reftype $var;
my $result;
eval {
$result = $var->{foo};
1; # guarantee a true result if code works.
}
or eval {
$result = $var->[3];
1;
}
or do {
$result = 'foo';
}
return $result;
}
在大多数情况下,我实际上并没有这样做,但在某些情况下我会这样做。我仍在考虑这种方法何时合适。我想我会把这个概念扔出去进一步讨论。我很想看 cmets。
更新
我意识到我应该就这种方法提出我的想法。
这种方法的优点是可以处理你扔给它的任何东西。
它的缺点是笨重,而且有些奇怪。在某些代码中偶然发现这一点会让我发出一个大胖子'WTF'。
我喜欢测试标量是否表现得像哈希引用,而不是它是否是哈希引用。
我不喜欢这个实现。
【讨论】:
我喜欢多态而不是手动检查:
use MooseX::Declare;
class Foo {
use MooseX::MultiMethods;
multi method foo (ArrayRef $arg){ say "arg is an array" }
multi method foo (HashRef $arg) { say "arg is a hash" }
multi method foo (Any $arg) { say "arg is something else" }
}
Foo->new->foo([]); # arg is an array
Foo->new->foo(40); # arg is something else
这比手动检查功能强大得多,因为您可以像使用任何其他类型约束一样重用“检查”。这意味着当您想要处理数组、散列和小于 42 的偶数时,您只需为“小于 42 的偶数”编写一个约束并为这种情况添加一个新的多方法。 “调用代码”不受影响。
你的类型库:
package MyApp::Types;
use MooseX::Types -declare => ['EvenNumberLessThan42'];
use MooseX::Types::Moose qw(Num);
subtype EvenNumberLessThan42, as Num, where { $_ < 42 && $_ % 2 == 0 };
然后让 Foo 支持这个(在那个类定义中):
class Foo {
use MyApp::Types qw(EvenNumberLessThan42);
multi method foo (EvenNumberLessThan42 $arg) { say "arg is an even number less than 42" }
}
然后Foo->new->foo(40) 打印arg is an even number less than 42 而不是arg is something else。
可维护。
【讨论】: