Perl 是不是支持函数调用中的命名参数?

Posted

技术标签:

【中文标题】Perl 是不是支持函数调用中的命名参数?【英文标题】:Does Perl support named parameters in function calls?Perl 是否支持函数调用中的命名参数? 【发布时间】:2012-11-13 12:19:17 【问题描述】:

根据我对支持该功能的语言的经验,使用命名参数而不是位置参数调用函数的程序更易于阅读和维护。

我认为 Perl 有这个功能,但它不适合我。

这是我正在使用的软件包的怪癖,还是我做错了?

设置函数调用

我的第一个 Perl 项目是使用 html::TableExtract 包从 HTML 标记中提取表格数据并将其显示为文本。

以下代码设置解析器:

use strict;
use warnings;
use HTML::TableExtract;

my $markup = <<MARKUP;
<table>
  <tr> <th>a</th> <th>b</th> <th>c</th> </tr>
  <tr> <td>1</td> <td>2</td> <td>3</td> </tr>
  <tr> <td>4</td> <td>5</td> <td>6</td> </tr>
</table>
MARKUP

my $parser = HTML::TableExtract->new() ;

$parser->parse($markup) ;

documentation 表示我可以使用tables_dump 方法将输出转储到命令提示符,并使用参数$show_content$col_sep 来控制输出格式:

tables_report([$show_content, $col_sep])

返回一个总结提取表的字符串,以及它们的深度和计数。可选地采用 $show_content 标志,它将转储每个表的提取内容以及由 $col_sep 分隔的列。默认 $col_sep 是 ':'。

tables_dump([$show_content, $col_sep])

与 tables_report() 相同,只是将信息转储到 STDOUT。

使用位置参数和命名参数调用

如果我按文档顺序传递位置参数,我会得到我期望的输出:

$parser->tables_dump(1, '_') ;

列用下划线而不是默认冒号分隔:

TABLE(0, 0):
a_b_c
1_2_3
4_5_6

按照 Perl.com 的Advance Subroutines article,我尝试传递一个包含参数名称和值的哈希来阐明参数的含义:

$parser->tables_dump(show_content => 1, col_sep => '_') ;

Perl 不明白这一点。它会忽略col_sep 的值并使用默认值输出:

TABLE(0, 0):
a:b:c
1:2:3
4:5:6

如果我不尝试更改分隔符,我会得到相同的输出:

$parser->tables_dump(show_content => 1) ;

即使我指定了无意义的参数名称,我也会得到相同的输出:

$parser->tables_dump(tweedledum => 1, tweedledee => '_') ;

我可以使用命名参数样式调用这个函数,还是应该满足于位置?

【问题讨论】:

【参考方案1】:

Perl 本身不支持命名参数,但是可以设计函数来接受命名参数(作为散列或 hashref)。函数的作者如何实现它取决于函数的作者。您需要提供函数所期望的参数,否则您会得到意想不到的结果。

【讨论】:

【参考方案2】:

Object Oriented Perl 第 6 章很好地解释了在 Perl 中传递的命名参数(即使使用默认值)。 这种风格非常重要,广泛用于对象构造函数中。这就是为什么在他们的 OO Perl 书中对其进行了解释。

我将引用他们的两个例子:

# This is how you call a subroutine using named argument passing
interests(name => "Paul", language => "Perl", favourite_show => "Buffy");

# This is how you define the subroutine to accept named arguments
sub interests 
   my (%args) = @_;

   # This is how you capture named arguments and define
   # defaults for the ones missing from a particular call.
   my $name           = $argsname           || "Bob the Builder";
   my $language       = $argslanguage       || "none that we know";
   my $favourite_show = $argsfavourite_show || "the ABC News";

   print "$name’s primary language is $language. " .
   "$name spends their free time watching $favourite_show\n";

另一个给出定义默认值的不同方式(在哈希中)的示例是:

my %defaults = ( pager => "/usr/bin/less", editor => "/usr/bin/vim" );

sub set_editing_tools 
    my (%args) = @_;

    # Here we join our arguments with our defaults. Since when
    # building a hash it’s only the last occurrence of a key that
    # matters, our arguments will override our defaults.
    %args = (%defaults, %args);

    # print out the pager:
    print "The new text pager is: $argspager\n";

    # print out the editor:
    print "The new text editor is: $argseditor\n";

【讨论】:

我不是在寻找一种方法来创建一个接受命名参数的模块;我认为 HTML::TableExtract 模块已经接受了命名参数。这个问题是基于一个错误的前提。但是,当我开始编写自己的 Perl 模块时,面向对象的 Perl 文档将非常有用。感谢分享。【参考方案3】:

在 Perl.com 的 Advance Subroutines 文章之后,我尝试通过 散列包含参数名称和值,以阐明含义 参数:

那篇文章介绍了一种编写子例程的方法,以便它们接受命名参数的 hashref。如果您调用的 sub 没有被写入接受它,那么它将不知道如何正确处理它。

$parser->tables_dump(show_content => 1, col_sep => '_') ;

Perl 不明白这一点。它忽略 col_sep 的值和 输出默认值:

不要过于迂腐,但 Perl 明白这一点。但是,tables_dump 仅用于接受标量参数列表。当您以这种方式调用它时,它会接收一个标量参数。这个参数恰好是对哈希的引用,但tables_dump 不知道也不关心它,所以它使用引用作为$show_content 的值。这可能相当于将1 传递给show_content,因为1 和任何可能的引用在布尔上下文中都将评估为“真”,我假设$show_content 只用作布尔值。

由于没有第二个参数,没有任何东西被分配给$col_sep,所以它使用默认分隔符,正如您所观察到的那样。

【讨论】:

感谢您解释观察到的行为。你是对的,但是 Perl 的理解不是我的意思! 归根结底,是我不理解 Perl。【参考方案4】:

Perl 没有对命名参数的内置支持。如果要使用它们,则必须专门编写函数以接受该样式的参数。所以你必须满足于位置参数(或编写一个包装函数(可能在一个子类中))。

【讨论】:

【参考方案5】:

这不是太复杂。您可以像散列或散列引用一样传递键值对,并在子例程中将参数加载到散列或散列引用中。

# called like $parser->tables_dump(show_content => 1, col_sep => '_') ;
sub TheParser::tables_dump 
    my ($self, $args) = @_;
    if ($args->show_content == 1) 
        print join $args->col_sep, $self->the_column_data();
        ...
    

通过另一行,您可以将您知道的命名参数加载到适当命名的变量中:

    my ($self, $args) = @_;
    my ($show_content, $col_sep) = @$argsqw(show_content col_sep);
    if ($show_content == 1) 
       ...

【讨论】:

对不起,我不明白。您是在向我展示如何使用接受命名参数的模块方法包装模块方法吗? 不,这是编写一个需要命名参数的新方法的方法。抱歉,如果这不是您所要求的。 我不是模块的维护者。我很乐意接受它所具有的任何限制。在考虑为其他人的代码提交补丁之前,我有很多关于 Perl 的知识要学习。尽管如此,感谢您的帮助!你的 sn-ps 给了我一些学习的指点。【参考方案6】:

离开标题的问题,您可以执行以下操作:

use strict;
use Carp::Assert;

sub func 
   my (%hash) = @_;
   assert($hash'baz' == 1);
   assert($hash'mu' == 2);


func('baz' => 1, 'mu' => 2);

【讨论】:

【参考方案7】:

这是我编写的一个简单程序,它可以使用一种命名参数。它允许使用默认值。

#!/usr/bin/perl
use strict;
use warnings;
use 5.014;
use POSIX qw/ strftime /;

# Script to get prior Monday (if today is Mon, then Mon a week ago).

for my $day (qw/ Sun Mon Tue Wed Thu Fri Sat /) 
    say "Last $day: ", last_monday( day => $day );  


sub last_monday 
    my %arg =  ( time => [localtime],
                 day  => 'mon',
                 span => 1,
                 @_
               );
    my $dow; # day of week

    if    ('sunday'    =~ /$argday/i)  $dow = 0
    elsif ('monday'    =~ /$argday/i)  $dow = 1
    elsif ('tuesday'   =~ /$argday/i)  $dow = 2
    elsif ('wednesday' =~ /$argday/i)  $dow = 3
    elsif ('thursday'  =~ /$argday/i)  $dow = 4
    elsif ('friday'    =~ /$argday/i)  $dow = 5
    elsif ('saturday'  =~ /$argday/i)  $dow = 6
    else 
        warn "$argday is not a valid day of week. $!";
        return;
    

    my ($wday, @dmy) = @ $argtime [6, 3..5];

    # (will work across month, year boundries)
    $dmy[0] -= ($wday - $dow) % 7 || ($argspan ? 7 : 0); # $dmy[0] == mday
    return strftime "%Y%m%d", 0,0,0,@dmy;

【讨论】:

你能在你的答案中使用更多的英语吗?我对 Perl 的理解还不够深入,看不出它是如何回答我的问题的。 @isme 对不起,我的答案很糟糕。我没有仔细阅读你的问题。如果我可以删除我的帖子,我会的。

以上是关于Perl 是不是支持函数调用中的命名参数?的主要内容,如果未能解决你的问题,请参考以下文章

在函数调用中“使用严格”和命名参数

将命名参数传递给 Django 模板中的函数

如何强制调用某些构造函数/函数以使用命名参数?

C++

面向对象的 Perl 构造函数语法和命名参数

Flutter 6种构造函数详解