首页 文章

在perl中,用默认参数调用多个子程序是不好的做法吗?

提问于
浏览
17

我正在学习perl并且理解使用shift解包子例程参数是一种常见且公认的做法 . 我也明白,省略函数参数以使用默认的 @_ 数组是常见且可接受的做法 .

考虑到这两件事,如果你调用一个没有参数的子程序, @_ 可以(并且如果使用shift)可以改变 . 这是否意味着使用默认参数调用另一个子例程,或者事实上,在此之后使用 @_ 数组被认为是不好的做法?考虑这个例子:

sub total { # calculate sum of all arguments
    my $running_sum;
    # take arguments one by one and sum them together
    while (@_) {
       $running_sum += shift;
    }
    $running_sum;
}

sub avg { calculate the mean of given arguments
    if (@_ == 0) { return }
    my $sum = &total; # gets the correct answer, but changes @_
    $sum / @_ # causes division by zero, since @_ is now empty
}

我的直觉告诉我,使用shift来解包参数实际上是不好的做法,除非你的子程序实际上应该改变传递的参数,但我已经在多个地方读过,包括Stack Overflow,这不是一个坏习惯 .

所以问题是:如果使用shift是常见的做法,我应该总是假设传递的参数列表可以更改,作为子例程的副作用(如引用示例中的 &total 子例程)?有没有办法按值传递参数,所以我可以确定参数列表不会被更改,所以我可以再次使用它(比如引用文本中的 &avg 子例程)?

7 回答

  • 2

    一般来说,参数中的 shift 是正确的 - 使用 & sigil来调用函数isn 't. (Except in some very specific situations you' ll可能永远不会遇到 . )

    您的代码可以重写,因此 total 不会 @_ 来自 @_ . 使用for循环甚至可能更有效 .

    sub total {
      my $total = 0;
       $total += $_ for @_;
      $total;
    }
    

    或者您可以使用 List::Util 中的 sum 函数:

    use List::Util qw(sum);
    
    sub avg { @_ ? sum(@_) / @_ : 0 }
    

    除了在面向对象的Perl中提取 $self 之外,使用 shift 并不常见 . 但是因为你总是把你的函数称为 foo( ... ) ,所以无论是 foo shift 还是 shift 都没有问题 .
    (关于函数唯一值得注意的是它是否分配给 @_ 中的元素,因为这些是作为参数给出的变量的别名 . 分配给 @_ 中的元素通常是坏的 . )

    即使您无法更改 total 的实现,使用显式参数列表调用sub也是安全的,因为参数列表是数组的副本:

    (a) &total - 使用相同的 @_ 调用 total ,并覆盖原型 .
    (b) total(@_) - 使用 @_ 的副本调用 total .
    (c) &total(@_) - 使用 @_ 的副本调用 total ,并覆盖原型 .

    表格(b)是标准的 . 表格(c)不应该使用原型),并且由于某些不明原因必须覆盖它们 . 证明设计不佳 . 形式(a)仅适用于尾调用( @_ = (...); goto &foo )或其他形式的优化(过早优化是所有邪恶的根源) .

  • 3

    你应该避免使用 &func; 的呼叫方式,除非你有充分的理由,并相信其他人也这样做 .

    要保护你的 @_ 不被被调用者修改,只需做 &func()func .

  • 0

    有时候Perl有点过于宽松,并且有多种方式访问输入参数可能会使代码变得异常和不一致 . 如果想要更好的答案,请尝试强加自己的标准 .

    这是我用过和看过的几种方式

    sub login
    {
        my $user = shift;
        my $passphrase = shift;
        # Validate authentication    
        return 0;
    }
    

    扩大 @_

    sub login
    {
        my ($user, $passphrase) = @_;
         # Validate authentication   
        return 0;
    }
    

    显式索引

    sub login 
    {
        my user = $_[0];
        my user = $_[1];
        # Validate authentication
        return 0;
    }
    

    使用函数原型强制执行参数(但这是not popular

    sub login($$)
    {
        my ($user, $passphrase) = @_;   
        # Validate authentication
        return 0;
    }
    

    遗憾的是,您仍然必须执行自己的复杂输入验证/污点检查,即:

    return unless defined $user;
    return unless defined $passphrase;
    

    或者更好的是,提供更多信息

    unless (defined($user) && defined($passphrase)) {
        carp "Input error: user or passphrase not defined";
        return -1;
    }
    

    Perldoc perlsub应该是你的第一个停靠港 .

    希望这可以帮助!

  • 2

    以下是一些谨慎使用 @_ 问题的例子 .

    1. Hash-y Arguments

    有时您想要编写一个可以获取键值对列表的函数,但其中一个是最常用的,您希望它在不需要键的情况下可用 . 例如

    sub get_temp {
      my $location = @_ % 2 ? shift : undef;
      my %options = @_;
      $location ||= $options{location};
      ...
    }
    

    所以现在如果你用奇数个参数调用函数,第一个是位置 . 这允许 get_temp('Chicago')get_temp('New York', unit => 'C') 甚至 get_temp( unit => 'K', location => 'Nome, Ak') . 这可能是您的用户更方便的API . 通过移动奇数参数,现在 @_ 是偶数列表并且可以被分配给散列 .

    2. Dispatching

    假设我们有一个类,我们希望能够按名称调度方法(可能AUTOLOAD可能很有用,我们将手动滚动) . 也许这是一个命令行脚本其中参数是方法 . 在这种情况下,我们定义两个调度方法一个"clean"和一个"dirty" . 如果我们用 -c 标志调用,我们就会得到干净的标志 . 这些方法按名称查找方法并调用它 . 不同的是如何 . 脏的一个离开自己在堆栈跟踪中,干净的一个必须是更多的切割器,但调度没有在堆栈跟踪中 . 我们制作了一个 death 方法,为我们提供了跟踪 .

    #!/usr/bin/env perl
    
    use strict;
    use warnings;
    
    package Unusual;
    
    use Carp;
    
    sub new { 
      my $class = shift;
      return bless { @_ }, $class;
    }
    
    sub dispatch_dirty { 
      my $self = shift;
      my $name = shift;
      my $method = $self->can($name) or confess "No method named $name";
      $self->$method(@_);
    }
    
    sub dispatch_clean { 
      my $self = shift;
      my $name = shift;
      my $method = $self->can($name) or confess "No method named $name";
      unshift @_, $self;
      goto $method;
    }
    
    sub death { 
      my ($self, $message) = @_;
      $message ||= 'died';
      confess "$self->{name}: $message";
    }
    
    package main;
    
    use Getopt::Long;
    GetOptions 
      'clean'  => \my $clean,
      'name=s' => \(my $name = 'Robot');
    
    my $obj = Unusual->new(name => $name);
    if ($clean) {
      $obj->dispatch_clean(@ARGV);
    } else {
      $obj->dispatch_dirty(@ARGV);
    }
    

    所以现在如果我们调用 ./test.pl 来调用死亡方法

    $ ./test.pl death Goodbye
    Robot: Goodbye at ./test.pl line 32
        Unusual::death('Unusual=HASH(0xa0f7188)', 'Goodbye') called at ./test.pl line 19
        Unusual::dispatch_dirty('Unusual=HASH(0xa0f7188)', 'death', 'Goodbye') called at ./test.pl line 46
    

    但是我们在踪迹中看到 dispatch_dirty . 如果我们调用 ./test.pl -c ,我们现在使用干净的调度程序并获取

    $ ./test.pl -c death Adios
    Robot: Adios at ./test.pl line 33
        Unusual::death('Unusual=HASH(0x9427188)', 'Adios') called at ./test.pl line 44
    

    这里的关键是goto(不是邪恶的goto),它使用子程序引用并立即使用当前 @_ 将执行切换到该引用 . 这就是为什么我必须 unshift @_, $self ,以便调用者为新方法做好准备 .

  • 20

    参考文献:

    sub refWay{
        my ($refToArray,$secondParam,$thirdParam) = @_;
        #work here
    }
    
    refWay(\@array, 'a','b');
    

    HashWay:

    sub hashWay{
       my $refToHash = shift; #(if pass ref to hash)
       #and i know, that:
       return undef unless exists $refToHash->{'user'};
       return undef unless exists $refToHash->{'password'};   
    
       #or the same in loop:
       for (qw(user password etc)){
           return undef unless exists $refToHash->{$_};
       }
    }
    
    hashWay({'user'=>YourName, 'password'=>YourPassword});
    
  • 0

    我尝试了一个简单的例子:

    #!/usr/bin/perl
    
    use strict;
    
    sub total {
    
        my $sum = 0;
        while(@_) {
            $sum = $sum + shift;
        }
        return $sum;
     }
    
    sub total1 {
    
    my ($a, $aa, $aaa) = @_;
    
    return ($a + $aa + $aaa);
    }
    
    my $s;
    $s = total(10, 20, 30);
    print $s;
    $s = total1(10, 20, 30);
    print "\n$s";
    

    这两个印刷语句的答案都是60 .

    但我个人觉得,这些论点应该以这种方式接受:

    my (arguments, @garb) = @_;
    

    为了避免后者出现任何问题 .

  • 0

    我在http://perldoc.perl.org/perlsub.html找到了以下宝石:

    “是的,仍有未解决的问题与@_的可见性有关 . 我暂时忽略了这个问题 . (但请注意,如果我们使@_词法作用域,那些匿名子程序可以像闭包一样...... (哎呀,听起来有点Lispish?(没关系 . )))“

    你可能遇到过这些问题之一:-(

    OTOH amon可能是对的 - > 1

相关问题