【问题标题】:Why does Programming Perl use local (not my) for filehandles?为什么 Programming Perl 使用本地(不是我的)文件句柄?
【发布时间】:2009-03-05 07:54:16
【问题描述】:

当我阅读Programming Perl,第 2 版,第 51 页时,有些事情让我感到困惑:

sub newopen {
    my $path = shift;
    local *FH;    #not my!
    open (FH, $path) || return undef;
    return *FH;
}

$fh = newopen('/etc/passwd');

我知道,为什么我们不推荐使用我的?到目前为止,我看不出如果我们使用 my() 会出什么问题。

谢谢!

【问题讨论】:

  • Programming Perl,第 3 版是很久以前在遥远的星系中编写的。而且你有一个比那个更旧的版本!
  • ...现在我们已经到了第 6 版,我想

标签: perl filehandle lexical


【解决方案1】:

老生常谈的答案是你必须使用local,因为my *FH 是一个语法错误。

“正确”(但不是很有启发性)的答案是您做错了。您应该改用词法文件句柄和 open 的三参数形式。

sub newopen {
    my $path = shift;
    my $fh;
    open($fh, '<', $path) or do {
        warn "Can't read file '$path' [$!]\n";
        return;
    }
    return $fh;
}

要真正回答为什么,需要解释词法变量和全局变量之间以及变量范围和持续时间之间的区别。

变量的范围是程序中其名称有效的部分。范围是一个静态属性。另一方面,变量的持续时间是动态属性。持续时间是程序执行期间变量存在并保存值的时间。

my 声明一个词法变量。词法变量的范围从声明点到封闭块(或文件)的末尾。您可以在不同的范围内拥有其他具有相同名称的变量而不会发生冲突。 (您也可以在重叠范围内重复使用名称,但不要这样做。)词法变量的持续时间是通过引用计数来管理的。只要至少有一个对变量的引用,该值就存在,即使该名称在特定范围内无效! my 还具有运行时效果——它分配一个具有给定名称的 new 变量。

local 有点不同。它对全局变量进行操作。全局变量具有全局范围(名称在任何地方都有效)和程序整个生命周期的持续时间。 local 所做的是对全局变量的 value 进行临时更改。这有时被称为“动态范围”。更改从local 声明处开始,一直持续到封闭块的末尾,之后旧值被恢复。重要的是要注意新值不限于块——它在任何地方都是可见的(包括被调用的子例程)。引用计数规则仍然适用,因此您可以在更改过期后获取并保留对本地化值的引用。

回到示例:*FH 是一个全局变量。更准确地说,它是一个“typeglob”——一组全局变量的容器。 typeglob 包含每个基本变量类型(标量、数组、散列)以及其他一些内容的插槽。从历史上看,Perl 使用 typeglobs 来存储文件句柄,local-izing 它们有助于确保它们不会相互干扰。词法变量没有类型团,这就是为什么说my *FH 是语法错误。

在现代版本的 Perl 中,词法变量可以而且应该用作文件句柄。这让我们回到了“正确”的答案。

【讨论】:

  • 谢谢。我喜欢这个“*FH 是一个全局变量。更准确地说,它是一个“typeglob”——一组全局变量的容器。它让我明白为什么需要 local()。
  • 好吧,你必须使用 local 因为像 *FH 这样的类型团是符号表的东西,而词法变量不处理符号表。我认为谈论范围来解释它会让人感到困惑。
【解决方案2】:

在您的示例代码中,对内置子例程open 的调用使用一个裸词作为文件句柄,这相当于一个全局变量。正如Nathan Fellman's answer 解释的那样,如果在脚本或模块的其他地方定义了另一个同名的全局变量,使用local 会将这个裸词本地化到当前代码块。这将防止先前定义的全局变量被新声明清除。

这在过去的 Perl 时代是一种非常普遍的做法,但 从 Perl 5.6 开始,最好使用标量(带有您在问题中暗示的 my 声明)来定义您的文件句柄,此外,使用三个参数调用open

use Carp;
open my $error_log, '>>', 'error.log' or croak "Can't open error.log: $OS_ERROR";

顺便说一句,请注意对于标准输入/输出的读写,最好还是使用两个参数open

use Carp;
open my $stdin, '<-' or croak "Can't open stdin: $OS_ERROR";

或者,您可以使用IO::File 模块来祝福类的文件句柄:

use IO::File;
my $error_log = IO::File->new('error.log', '>>') or croak "Can't open error.log: $OS_ERROR");

这里的大部分功劳归于Damian Conway, author of the excellent book Perl Best Practices。如果您对 Perl 开发很认真,那么您应该购买这本书。

【讨论】:

  • +1。基本上,在 5.6 之前,文件句柄在 Perl 中并不是一流的值,需要各种令人作呕的 hack 才能将它们传入和传出函数。另外使用新的 3-arg open() 语法——否则不寻常的文件名会导致各种混乱。
  • 希望很明显我的请求是针对提问者的 :)
【解决方案3】:

你为什么要读一本过时的书。第三版已经出很久了!您使用的是哪个版本的 Perl?第 2 版描述了 Perl 5.004 (5.4.x) 或更高版本。

现在,您不应该将 typeglob 表示法用于文件句柄;使用“词法文件句柄”(我认为请参见 open)或 FileHandle 模块,或其亲属之一。


感谢 Michael Schwern 和 Ysth 将 cmets 纳入此处。

【讨论】:

  • 在现实世界中,您确实需要处理大量遗留代码。
  • 第二版是 5.004ish,我认为(现代说法是 5.4.x)。
  • @Yan 在现实世界中,很少有人使用超过 5.6 的版本。 @Jonathan FileHandle 对于打开和读取文件来说太过分了。词法文件句柄++
【解决方案4】:

我相信这是因为my 在堆栈上分配了变量的新副本,并且在退出块时它会丢失。 local 将现有的*FH 保存在别处并覆盖现有的*FH。当您退出堆栈时,它会恢复旧的。使用 my 时,*FH typeglob 会在您退出块时超出范围。使用local,它会一直存在,因此您可以在退货后继续使用它。

我不能 100% 确定这一点,但也许它可以为您指明正确的方向。

【讨论】:

  • 你说对了,也说错了,而且很模糊。 :D 完整的解释太长了,无法放在评论中,所以我添加了一个新的答案(甚至那是一个删节版)。
【解决方案5】:

请参阅本地化文件句柄 here,我想这可以解释它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-17
    相关资源
    最近更新 更多