Perl 使用变量来引用模块会弄乱传递参数

Perl using a variable to reference a module messes up passing parameters

我在使用变量引用模块时遇到问题,似乎弄乱了变量的传递:

TOTO.pm

package TOTO;

use Data::Dumper;

sub print {
       print Dumper(@_);
}

Perl 程序

package main;

TOTO::print('Hello World');
print ">>>>>>>>>>>\n";
my $package = 'TOTO';

$package->print('Hello World');

输出为:

$VAR1 = 'Hello World';
>>>>>>>>>>>
$VAR1 = 'TOTO';
$VAR2 = 'Hello World';

关于如何避免将 TOTO 作为第一个变量传递的任何建议?

这就是 Perl 的包系统的工作原理。您需要在被调用的子程序中自行处理。您无法在通话前更改它。

sub print {
    # special variable __PACKAGE__ contains "TOTO"

    if ($_[0] eq __PACKAGE__ || ref $_[0] eq __PACKAGE__){
        shift; # throw away class/object
    } 
    print Dumper(@_);
}

技术上不需要 ref $_[0] 部分,因为您的 class 中没有构造函数(您只调用 class 上的方法,但它会如果您确实使用对象而无需稍后更改任何内容,则只需做正确的事即可。

简短: 观察到的行为来自对包名称使用 ->

箭头运算符与引用或对象一起使用,对象本身是对已 bless 编辑到其 class 中的数据结构的引用。 (或使用 class 名称,见下文。)该对象或 class 名称作为第一个参数悄悄传递,以便整个系统正常工作。请注意,问题中的包 而不是 定义了 class (不能用它创建对象)。

来自Arrow operator in perlop

"-> " is an infix dereference operator, just as it is in C and C++. If the right side is either a [...] , {...} , or a (...) subscript, then the left side must be either a hard or symbolic reference to an array, a hash, or a subroutine respectively. (Or technically speaking, a location capable of holding a hard reference, if it's an array or hash reference being used for assignment.) See perlreftut and perlref.

它继续,对这个问题有直接兴趣的陈述

Otherwise, the right side is a method name or a simple scalar variable containing either the method name or a subroutine reference, and the left side must be either an object (a blessed reference) or a class name (that is, a package name). See perlobj.

因此在与 classes 相关的使用中,左侧可能包含 class 名称,然后可以在其上调用 class 方法(或者它可以只是查询)。假设 class 是一个包,那么这个 一个包名。

问题中的情况属于这种情况,因此将包名称传递给子程序。但是,根据上面的引述,sub 似乎只能是 method,这里不是这种情况。所以可能真的应该禁止使用 ->。无论哪种方式,在不是 class 的包裹上使用它都让我感到误会。


更新说明。此用途旨在解决加载包的歧义。包名称被保存到一个变量中,然后使用箭头运算符对其调用子程序。在这种情况下,代码必须添加到 sub 以处理第一个参数(包名称),无论调用如何,由箭头运算符提供。但是,我们将不得不允许这样一种情况,即 this is 在一个对象上调用,最终得到一个涵盖两种不同用途的代码。我相信还是换个不涉及这一切的设计比较好。


如果你想使用一个包,比如一个库

文件TOTO.pm

pacakge TOTO;

use Exporter;
our (@ISA, @EXPORT_OK);
@ISA = ('Exporter');
@EXPORT_OK = qw(prn);   # This can be asked for by user of package

use Data::Dumper;

sub prn {
       print Dumper(@_);
}

1;  # important for 'require' when this is used

我已将子名称更改为 prn,这样它就不是 Perl 库函数了。主脚本

use warnings;
use strict;

use TOTO qw(prn);

prn("Hello World");

完全限定名称 TOTO::prn() 始终可以使用。如果你想让它成为一个 class,那将需要更多的包。

这个包,TOTO,默认情况下不导出任何东西,除非被要求。这就是 @EXPORT_OK 设置的内容,这就是为什么我们需要列出要在 use TOTO 时导入到 main:: 的函数。例如,从 perlmod

开始

检查两者之间的差异:

TOTO::print("Hello World");

TOTO->print("Hello World");

这不是正确的对象表示法,因为 TOTO 只是一个字符串。

语法 object->function(arguments)object 作为第一个参数传递,存储为 $this,示例。

sub print {
    my $this = shift @_;
    print Dumper(@_);
}

可以做这份工作(即使不是祝福对象)。

试试这个:

package TOTO;
use Data::Dumper;
sub new { return bless {}, shift; }

sub print {
    my $self = shift @_;
    if   ( scalar $self =~ /=HASH\(/ ) {
        print Dumper(@_);
    } else {
        print Dumper($self);
    }
}

package main;

my $package = TOTO->new();

$package->print("Hello World");

TOTO::print("Hello World");

这可能输出:

$VAR1 = 'Hello World';
$VAR1 = 'Hello World';

并查看 man perlobjman perlootutman perlmodlib

用最简单的术语来说,要创建一个面向对象的 TOTO 模块,您必须创建一个文件 TOTO.pm,其中至少包含一个构造子例程 new

package TOTO;

sub new {
    bless {};
}

sub print {
    print "I am a TOTO object\n";
}

1;

该代码必须保存在名为 TOTO.pm 的文件中,该文件必须与源 package TOTO 中的名称相匹配

然后您可以编写一个使用该模块的程序,比如 main.pl。例如

use strict;
use warnings 'all';

use TOTO;

my $object = TOTO->new;

$object->print;

然后您创建了一个新的 TOTO 对象,说明它是什么

如果我运行

$ perl main.pl

我得到了输出

I am a TOTO object

您会想让这段代码更有用,而且这个主题有很多变体,但这些是基础

问题来了

Any advice on how to avoid having TOTO passed as the first variable?

您自己找到了答案。这工作正常

TOTO::print('Hello World');

如果你称它为

TOTO->print('Hello World');

然后你要求 perl 调用 print 作为 class 方法并将 ('TOTO', 'Hello World') 作为参数传递给 TOTO::print 子例程

如果 TOTO 只是一堆子程序,那么,如您所见,只需调用 TOTO::totosub