【问题标题】:How do I tell what type of value is in a Perl variable?如何判断 Perl 变量中的值是什么类型?
【发布时间】:2010-12-16 10:10:06
【问题描述】:

如何判断 Perl 变量中的值是什么类型?

$x 可能是标量、数组的引用或哈希的引用(或者可能是其他东西)。

【问题讨论】:

    标签: perl


    【解决方案1】:

    ref():

    Perl 提供了ref() 函数,以便您可以在取消引用之前检查引用类型...

    通过使用ref() 函数,您可以保护取消引用变量的程序代码在使用错误类型的引用时不会产生错误...

    【讨论】:

    • 我认为 ref() 只会告诉你它是什么类型的引用,如果不是,则什么也不返回。
    • @Chris:是的,所以如果变量不是引用,你可以从它什么都不返回的事实推断它是一个简单的标量。否则,您将知道它是哪种参考。
    • @pm100:不客气;也许你可以编辑你的问题,说明你有什么限制?
    • @Adam:我想我的想法更笼统,但 OP 确实说了 $x(而不是 @x 或 %x)。所以,你是对的。
    • 我认为 pm100 是在谈论 SO 的 15 字符注释限制。
    【解决方案2】:

    $x 始终是一个标量。提示是符号$:以$ 开头的任何变量(或其他类型的解引用)都是标量。 (有关数据类型的更多信息,请参阅perldoc perldata。)

    引用只是一种特定类型的标量。 内置函数ref 会告诉你它是什么类型的引用。另一方面,如果你有一个祝福引用,ref 只会告诉你引用被祝福到的包名称,而不是数据的实际核心类型(祝福引用可以是 hashrefs、arrayrefs 或其他东西)。你可以使用Scalar::Utilreftype 会告诉你它是什么类型的参考:

    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 违反封装,因此除非您有充分的理由,否则应避免使用。
    • 如果您确实使用了 reftype,请注意它会为非引用返回 undef,因此像 reftype($x) eq 'HASH' 这样的代码可能会导致警告。 (另一方面,ref 方便地为非引用返回 ''。)
    • @ysth:确实如此!我已经更新了我的回复.. 很少能在祝福对象上找到 ref 的好用处。
    • (呃,我的意思当然是reftype。):) 如果需要直接访问一个受祝福的对象,应该有一个访问器方法来抽象出实现细节。
    【解决方案3】:

    标量始终包含单个元素。标量变量中的任何内容始终是标量。引用是一个标量值。

    如果你想知道它是否是参考,你可以使用ref。如果您想知道引用类型, 您可以使用来自Scalar::Utilreftype 例程。

    如果你想知道它是否是一个对象,你可以使用来自Scalar::Utilblessed 例程。不过,你永远不应该关心祝福的包裹是什么。 UNIVERSAL 有一些方法可以告诉你一个对象:如果你想检查它是否有你想调用的方法,使用can;如果你想看到它继承自某些东西,请使用isa;如果您想查看对象处理角色,请使用DOES

    如果您想知道该标量是否实际上只是一个标量但与一个类相关联,请尝试tied。如果你得到一个对象,继续你的检查。

    如果你想知道它是否看起来像一个数字,你可以使用Scalar::Util中的looks_like_number。如果它看起来不像一个数字并且它不是一个引用,那么它就是一个字符串。但是,所有简单值都可以是字符串。

    如果你需要做一些更花哨的事情,你可以使用Params::Validate这样的模块。

    【讨论】:

      【解决方案4】:

      在某个时候,我在 Perlmonks 上读到了一个相当有说服力的论点,即用 refreftype 测试标量的类型是一个坏主意。我不记得是谁提出了这个想法或链接。对不起。

      关键是,在 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'。

      我喜欢测试标量是否表现得像哈希引用,而不是它是否是哈希引用。

      我不喜欢这个实现。

      【讨论】:

      • 我可能就是你想的那个人。我说永远不要测试文字字符串。针对原型进行测试:if(ref $f eq ref {})。至于 tie,你从 tie() 开始:如果你得到一个对象,做正常的事情。
      • 请永远,永远不要以这种方式实际测试 refs。做起来很容易。 :)
      • 我不喜欢文字字符串,因为人们倾向于输入错误或记错它们。这是任何“魔术字符串”作为源代码或常量的更普遍问题的一部分。由于有一种方法可以在没有特殊魔术字符串知识的情况下做到这一点,所以我这样做了。
      • 如果您想验证,例如,您所期望的 hashref 实际上是一个 hashref,那么类似行为的测试非常棒。一系列 ifs 打破了这种方法的优势,即正确处理多个重载的 deref 类型。 (虽然 subs 的自动创建使得类似代码引用的行为测试无法正确进行,并且伪散列曾经也是一个问题。)
      • “行为类似”测试的问题在于,即使对于访问,绑定变量也可能产生副作用。已经有一些方法可以在不实际对其进行任何操作的情况下对变量进行内省。
      【解决方案5】:

      我喜欢多态而不是手动检查:

      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-&gt;new-&gt;foo(40) 打印arg is an even number less than 42 而不是arg is something else

      可维护。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-09-05
        • 1970-01-01
        • 2011-05-23
        • 2012-06-16
        • 1970-01-01
        • 1970-01-01
        • 2010-10-06
        • 1970-01-01
        相关资源
        最近更新 更多