Perl:如何动态创建对象?

14
我的目标是能像这样使用$obj:
print $obj->hello() . $obj->{foo};

我想要创建一个内联对象,也许可以使用类似于这样的东西:

my $obj = (
    foo => 1,
    hello => sub { return 'world' }
);

但是当我尝试将 $obj 作为对象使用时,出现了一个错误,错误信息为 $obj未被祝福。是否有一些基类(例如PHP中的stdClass),可以用来使哈希表得到祝福,这样我就可以将其作为对象使用了?


对于那些了解JavaScript的人,我正在尝试在Perl中做以下操作:

# JS CODE BELOW
var obj = { foo: 1, hello: function () { return 'world' } };
echo obj.hello() + obj.foo;

Perl的方法不是哈希表中的字段——这不是Java。 - Ether
2
JavaScript 不是 Java。 - cjm
8个回答

14
Perl需要一点帮助才能实现这个功能,因为它不认为存储在哈希表中的代码引用是“方法”。方法是作为包符号表的条目实现的。Perl比JavaScript更加面向类,而JavaScript则自豪地宣称它更加面向对象(对于单个对象来说)。
为了实现该功能,您需要创建一个将引用映射到此方式的类。绕过符号表中的方法的方法是使用AUTOLOAD方法。如果包含AUTOLOAD子例程的包在继承链中找不到Perl无法找到的受保护对象时,它将调用AUTOLOAD并设置软件包范围的(our)变量$AUTOLOAD将包含函数的完整名称。
我们通过获取完全限定子名称中最后一个节点(最后一个'::'之后)来获取调用的方法的名称。我们查看该位置是否有代码引用,如果有,我们可以返回它。
package AutoObject;

use strict;
use warnings;
use Carp;
use Params::Util qw<_CODE>;
our $AUTOLOAD;

sub AUTOLOAD {
    my $method_name = substr( $AUTOLOAD, index( $AUTOLOAD, '::' ) + 2 );
    my ( $self )    = @_;
    my $meth        = _CODE( $self->{$method_name} );
    unless ( $meth ) { 
        Carp::croak( "object does not support method='$method_name'!" );
    }
    goto &$meth;
}


1;

然后你会将对象转换为该类:

package main;

my $obj 
    = bless { foo => 1
      , hello => sub { return 'world' }
      }, 'AutoObject';

print $obj->hello();

通常,在 AUTOLOAD 子程序中,我会 "固定" 行为。也就是说,我会在包符号表中创建条目,以避免下次使用 AUTOLOAD。但这通常是针对一个相当明确定义的类行为。

我还设计了一个 QuickClass,它为每个声明的对象创建一个包,但其中涉及到很多符号表操作,现在可能最好使用 Class::MOP来完成。


根据 Eric Strom 的建议,您可以将以下代码添加到 AutoObject 包中。每当有人使用 AutoObject(带参数 'object')时,import子程序都会被调用。

# Definition:
sub object ($) { return bless $_[0], __PACKAGE__; };

sub import { # gets called when Perl reads 'use AutoObject;'
    shift; # my name
    return unless $_[0] eq 'object'; # object is it's only export
    use Symbol;
    *{ Symbol::qualify_to_reference( 'object', scalar caller()) }
        = \&object
        ;
}

然后,当你想要创建一个“对象字面量”时,你只需要这样做:

use AutoObject qw<object>;

表达式将会是:

object { foo => 1, hello => sub { return 'world' } };

你甚至可以这样做:

object { name  => 'World'
       , hello => sub { return "Hello, $_[0]->{name}"; } 
       }->hello()
       ;

你有一个"对象字面量"表达式。也许更好的称呼这个模块是Object::Literal


这实际上是一个很方便的技巧。如果我在 Perl 中使用 OOP,我可能会好好利用它。 - Jon Purdy
你可以通过将bless语句放在一个子例程中来使调用代码更加简洁:sub object {bless $_[0] => 'AutoObject'},然后使用my $obj = object {...};来创建对象。 - Eric Strom
@Eric Strom:这很有道理。但是我想,JavaScript的习惯用法基本上是“对象字面量”。在Perl中,“对象字面量”通常是bless { ... }, 'MyClass'。当然,由于bless只是一个函数,所以没有理由认为object { ... }不是更好的字面量。我会修改我的代码。 - Axeman

5

更加 Perl 风格的方法是为您的对象的所需方法创建一个单独的命名空间,并使用 bless 将对象赋予这些方法,使其可用于您的对象。实现此方法的代码仍然可以非常简洁。

my $obj = bless { foo => 1 }, "bar";
sub bar::hello { return 'world' };

正如gbacon所建议的,如果你愿意写$obj->{hello}->()而不是$obj->hello(),那么你可以省略bless操作。
my $obj = { foo => 1, hello => sub { return 'world' } };

4

2

$obj应该是一个标量,因此你所分配的任何内容也必须是标量。你可以这样说:

my %obj = ( foo => 1, hello => sub { return 'world' });

或者

my $obj = { foo => 1, hello => sub { return 'world' }};

后者使用花括号创建哈希引用(是标量,所以可以放入$obj中)。但要访问哈希引用内部的内容,必须使用箭头运算符。类似于$obj->{foo}&{$obj->{hello}}
除非您需要有哈希列表之类的东西,否则通常最好使用第一种方法。
无论哪种方法,您都无法说$obj->hello()。 Perl使用该语法进行自己的OOP,其中hello函数位于单独的包中,您的引用已被bless。例如:
package example;
sub new {} { my $result = {}; return bless $result, 'example' }
sub hello { return 'world' }

package main;
my $obj = example->new();

正如您所看到的,您可以调用的方法已经定义好了,要添加更多方法并不容易。虽然有一些魔法方法可以实现这样的事情,但真的不值得去做。使用&{$obj{hello}}(对于引用,可以使用&{$obj->{hello}} )比尝试让 Perl 像 Javascript 一样工作更容易。


2
无论在哪个函数中创建对象,您都需要调用bless来启用方法调用。
例如:
package MyClass;

sub new
{
  my $obj = {
    foo => 1
  };

  return bless($obj, "MyClass");
}

sub hello
{
  my $self = shift;
  # Do stuff, including shifting off other arguments if needed
}

package main;
my $obj = MyClass::new();

print "Foo: " . $obj->{foo} . "\n";
$obj->hello();

编辑:如果你想使用子例程引用为对象提供动态功能...

首先,你可以像下面这样创建你的代码引用(在这个哈希构造器的例子中):

my $obj = {
  foo => 1,
  hello => sub { print "Hello\n"; },
}

您可以像这样调用它:
my $obj = MyClass::new(); # or whatever
$obj->{hello}->(@myArguments);

有点繁琐,但是它可以运作。(你甚至可能不需要第二个箭头,但我不确定。)

2
在 Perl 中,它的拼写略有不同:
my $obj = { foo => 1, hello => sub { return "world" } };
print $obj->{hello}() . $obj->{foo};

但是这段代码很笨拙。你看到的关于引用没有被祝福的警告告诉你,你的对象没有按照Perl期望的方式实现。bless操作符会将一个对象标记为其方法开始搜索的包。
告诉我们你在问题域中想要做什么,我们可以为你提供更自然的Perl解决方案建议。

0

我建议按照perltoot手册中所述的使用Class::Struct。

不要用自己的话来解释文档,让我引用一下它的原话,因为它很好地解释了这个工作原理:

“它提供了一种将类声明为具有特定类型字段对象的方法。做这件事的函数被称为struct()。因为在Perl中结构或记录不是基本类型,所以每次您想创建一个类来提供类似记录的数据对象时,您都必须定义一个新的()方法,以及每个记录字段的单独数据访问方法。你很快就会对这个过程感到厌倦。Class::Struct::struct()函数减轻了这种乏味。”

仍然引用文档中的内容,以下是实现它的示例方式:

use Class::Struct qw(struct);
use Jobbie;  # user-defined; see below
struct 'Fred' => {
    one        => '$',
    many       => '@',
    profession => 'Jobbie',  # does not call Jobbie->new()
};
$ob = Fred->new(profession => Jobbie->new());
$ob->one("hmmmm");
$ob->many(0, "here");
$ob->many(1, "you");
$ob->many(2, "go");
print "Just set: ", $ob->many(2), "\n";
$ob->profession->salary(10_000);

0
Perl中的方法不像Python中那样是对象的属性。方法是与对象相关联的中的普通常规函数。这些常规函数需要一个额外的参数来引用自身。
您不能将动态创建的函数作为方法。
以下是perldoc perlobj中的一句话:
   1.  An object is simply a reference that happens to know which class it
       belongs to.

   2.  A class is simply a package that happens to provide methods to deal
       with object references.

   3.  A method is simply a subroutine that expects an object reference
       (or a package name, for class methods) as the first argument.

哦,而bless()是你建立引用和包之间连接的方式。


不确定您所说的“无法将动态创建的函数作为方法”是什么意思。人们可以动态生成函数并将它们插入到包中。许多CPAN模块都这样做。例如,可以看看Getopt::Long::Descriptive。对于用户请求的每个选项解析器,它都会生成一个新的类名。在该包中,它为用户定义的每个命令行选项安装一个getter。 - FMc
我的意思是,你不能像他想要的那样添加一个方法。动态函数插入要比这个(“no strict ref; *{“$package::function”} = sub{...};”)棘手得多,并且不应该轻易尝试。 - user3458

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接