Perl子例程参数

时间:2013-10-07 20:33:30

标签: perl

我最近一直在阅读关于Perl的内容,并且对于Perl如何处理传递给子例程的参数感到有些困惑。

在Python,Java或PHP等语言中,函数定义采用的形式(伪代码):

function myFunc(arg1, arg2) {
    // Do something with arg1 and arg2 here
}

然而在Perl中,它只是:

sub mySub {
    # @_ holds all arguments passed
}

据我所知,这是唯一的方法。

  • 如果我想限制调用者只传递2个参数怎么办?

  • 这不仅仅是Perl在其他语言(即Python,C等)中不允许任何变量号参数吗?

  • 在某些时候这不会成为问题吗?

  • 其他语言的所有默认参数号检查怎么样?是否必须在Perl中明确地做到这一点?例如

    sub a_sub {
        if (@_ == 2) {
            # Continue function
        }
        else {
            return false
        }
    }
    

7 个答案:

答案 0 :(得分:73)

您对Perl环境持谨慎态度,因为它与您之前遇到的语言完全不同。

相信强类型和功能原型的人会在这里不同意,但我相信这样的限制很少有用。 C 真的是否抓住了你将错误数量的参数传递给一个经常足够有用的函数?

现代Perl中最常见的是将@_的内容复制到词法标量变量列表中,因此您经常会看到以

开头的子程序
sub mysub {
  my ($p1, $p2) = @_;
  ... etc.
}

这样,传递的所有参数将作为@_$_[0]$_[1]等)的元素提供,而预期其中一些已命名并显示在$p1$p2中(尽管我希望您了解这些名称应该正确选择)。

在子例程是方法的特定情况下,第一个参数是特殊的。在其他语言中,它是selfthis,但在Perl中,它只是@_中的第一个参数,您可以根据自己的喜好调用它。在那种情况下,你会看到

sub method {
  my $self = shift;
  my ($p1, $p2) = @_;
  ... etc.
}

这样上下文对象(或类的名称,如果它是一个类方法)被提取到$self(约定的名称),其余参数保留在@_直接访问,或者更常见的是,将其复制到$p1$p2等本地标量变量。

最常见的抱怨是没有类型检查,所以我可以传递我喜欢的任何标量作为子例程参数。只要use strictuse warnings在上下文中,即使这通常很容易调试,只是因为子例程可以在一种标量形式上执行的操作在另一种形式上通常是非法的。

虽然最初更多的是关于面向对象的Perl封装,但Larry Wall的这句话非常相关

  

Perl对强制隐私没有迷恋。因为你没有被邀请,所以你宁愿呆在客厅外面,也不是因为它有霰弹枪

C是在主要效率提升的日子里设计和实现的,如果你可以在编译期间而不是在运行时使错误的程序失败。现在情况发生了变化,虽然客户端JavaScript出现了类似的情况,但在从互联网上获取必须处理的数据之前,它实际上对于知道代码是错误的有用。遗憾的是,JavaScript参数检查现在比它应该更宽松。


<强>更新

对于那些怀疑Perl用于教学目的的人,我建议它正是因为 Perl的机制如此简单和直接,以至于它们理想用于此类目的

  • 当您调用Perl子例程时,调用中的所有参数都在@_别名。您可以直接使用它们来影响实际参数,或者复制它们以防止外部操作

  • 如果将Perl子例程作为方法调用,则调用对象或类将作为第一个参数提供。同样,子例程(方法)可以使用@_

  • 执行它喜欢的操作

答案 1 :(得分:21)

Perl不会为您管理您的参数处理。相反,它提供了一个最小的,灵活的抽象,允许您编写符合您需求的代码。

按参考传递

默认情况下,Perl为@_中的每个参数添加一个别名。这实现了基本的,按引用传递语义。

my $num = 1;
foo($num);
print "$num\n";  # prints 2.

sub foo { $_[0]++ }

通过引用传递速度很快,但存在泄漏参数数据更改的风险。

按副本传递

如果您想要传递副本语义,则需要自己制作副本。处理位置参数列表的两种主要方法在Perl社区中很常见:

sub shifty {
    my $foo = shift;
}

sub listy {
    my ($foo) = @_;
}

在我的工作地点,我们做了一个listy的版本:

sub fancy_listy {

    my ($positional, $args, @bad) = @_;

    die "Extra args" if @bad;
}

命名参数

另一种常见做法是使用命名参数

sub named_params {
    my %opt = @_;
}

有些人对上述情况感到满意。我更喜欢更冗长的方法:

sub named_params {
    my %opt = @_;

    my $named = delete $opt{named} // "default value";
    my $param = delete $opt{param}
        or croak "Missing required 'param'";

    croak "Unknown params:", join ", ", keys %opt
        if %opt;

    # do stuff 
}

将已命名的params解包为变量,允许空间进行基本验证和默认值,并强制不传递额外的未知参数。

关于Perl原型

Perl的“原型”在正常意义上是 原型。它们仅提供编译器提示,允许您跳过函数调用的括号。唯一合理的用途是模仿内置函数的行为。您可以轻松地击败原型参数检查。一般来说, 不要使用原型 。小心使用它们会使用运算符重载 - 即。谨慎而且只是为了提高可读性。

答案 2 :(得分:8)

出于某种原因,Perl喜欢列表,并且不喜欢静态类型。 @_数组实际上提供了很大的灵活性,因为子例程参数是通过引用传递 ,而不是通过值传递 。例如,这允许我们做out-arguments:

my $x = 40;
add_to($x, 2);
print "$x\n"; # 42

sub add_to { $_[0] += $_[1] }

......但这更像是历史性的表演黑客。通常,参数由列表赋值“声明”:

sub some_sub {
  my ($foo, $bar) = @_;
  #               ^-- this assignment performs a copy
  ...
}

这使得这个sub-call-by-value的语义更为理想。是的,简单地忘记了未使用的参数,并且太少的参数不会引发任何自动错误 - 变量只包含undef。您可以添加任意验证,例如通过检查@_的大小。


有计划最终在将来提供命名参数,看起来像

sub some_sub($foo, $bar) { ... }

如果安装signatures模块,则可以使用今天语法。但有一些更好的东西:我强烈推荐Function::Parameters,它允许语法如

fun some_sub($foo, $bar = "default value") { ... }

method some_method($foo, $bar, :$named_parameter, :$named_with_default = 42) {
  # $self is autodeclared in methods
}

这也支持实验型检查。

解析器扩展FTW!

答案 3 :(得分:6)

如果您真的想在Perl中强制执行更严格的参数检查,可以查看类似Params::Validate的内容。

答案 4 :(得分:4)

Perl确实具有参数占位符的prototyping功能,您可以常常看到它,但它通常是不必要的。

sub foo($){
    say shift;
}; 
foo();      # Error: Not enough arguments for main::foo
foo('bar'); # executes correctly

如果你做sub foo($$){...},则需要2个非可选参数(例如foo('bar','baz')

答案 5 :(得分:3)

你可以使用:

my ($arg1, $arg2) = @_;

明确限制您可以使用的参数数量:

my $number =2;
die "Too many arguments" if @_ > $number;

答案 6 :(得分:1)

如果您最近正在阅读关于perl的内容,请阅读最近的perl。您也可以免费阅读 Modern Perl 书籍:free online Modern Perl book

以下是该书中关于功能签名的几个例子:

sub greet_one($name = 'Bruce') { 
    say "Hello, $name!"; 
}
sub greet_all($leader, @everyone) { 
    say "Hello, $leader!"; 
    say "Hi also, $_." for @everyone; 
}
 sub make_nested_hash($name, %pairs) { 
    return { $name => \%pairs }; 
}