11

当我通读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() 会出什么问题。

谢谢!

4

5 回答 5

27

陈词滥调的答案是你必须使用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还具有运行时效果——它分配一个具有给定名称的变量。

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

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

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

于 2009-03-05T20:42:32.800 回答
20

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

这在过去的 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");

这里的大部分功劳归功于优秀著作 Perl Best Practices 的作者 Damian Conway。如果您对 Perl 开发很认真,那么您应该自己购买这本书。

于 2009-03-05T09:08:45.183 回答
12

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

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


感谢 Michael Schwern 和 Ysth 在此处提供的评论。

于 2009-03-05T08:02:50.350 回答
0

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

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

于 2009-03-05T08:47:35.530 回答
0

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

于 2009-03-05T09:06:58.060 回答