在调查 scoping in Perl and Python 时,我遇到了Perl的静默范围相关行为,这可能导致很难追踪错误 . 特别适合那些不熟悉该语言并且不完全了解其语言细节的程序员 . 我已经为Perl和Python提供了示例代码,以说明作用域在两种语言中的工作原理
在Python中,如果我们运行代码:
x = 30
def g():
s1 = x
print "Inside g(): Value of x is %d" % s1
def t(var):
x = var
print "Inside t(): Value of x is %d" % x
def tt():
s1 = x
print "Inside t()-tt(): Value of x is %d" % x
tt()
g()
t(200)
结果输出是:
Inside t(): Value of x is 200
Inside t()-tt(): Value of x is 200
Inside g(): Value of x is 30
这是通常的词法范围行为 . Python默认将块中的赋值视为新变量的定义和赋值,而不是封闭范围中可能存在的全局变量 . 要覆盖此行为,需要显式使用关键字 global
来修改全局变量x . 函数 g()
中变量 x
的范围由程序中定义它的位置确定,而不是函数 g()
被调用的位置 . 由于Python中的词法作用域行为,当函数 g()
在函数 t()
中调用时,其中另一个词法作用域变量 x
也被定义并设置为值200, g()
仍然显示旧值30,因为这是值范围中的变量x,其中定义了 g()
. 函数 tt()
为 tt()
的词法范围内的变量 x
显示值200 . Python只有词法范围,这是默认行为 .
相比之下,Perl提供了使用词法作用域和动态作用域的灵活性 . 在某些情况下,这可能是一个福音,但如果程序员不小心并且理解在Perl中如何使用作用域,也可能导致很难找到错误 .
为了说明这种微妙的行为,如果我们执行以下Perl代码:
use strict;
our $x = 30;
sub g {
my $s = $x;
print "Inside g\(\)\: Value of x is ${s}\n";
}
sub t {
$x = shift;
print "Inside t\(\)\: Value of x is ${x}\n";
sub tt {
my $p = $x;
print "Inside t\(\)-tt\(\)\: Value of x is ${p}\n";
}
tt($x);
g();
}
sub h {
local $x = 2000;
print "Inside h\(\)\: Value of x is ${x}\n";
sub hh {
my $p = $x;
print "Inside h\(\)-hh\(\)\: Value of x is ${p}\n";
}
hh($x);
g();
}
sub r {
my $x = shift;
print "Inside r\(\)\: Value of x is ${x}\n";
sub rr {
my $p = $x;
print "Inside r\(\)-rr\(\)\: Value of x is ${p}\n";
}
rr($x);
g();
}
g();
t(500);
g();
h(700);
g();
r(900);
g();
结果输出是:
Inside g(): Value of x is 30
Inside t(): Value of x is 500
Inside t()-tt(): Value of x is 500
Inside g(): Value of x is 500
Inside g(): Value of x is 500
Inside h(): Value of x is 2000
Inside h()-hh(): Value of x is 2000
Inside g(): Value of x is 2000
Inside g(): Value of x is 500
Inside r(): Value of x is 900
Inside r()-rr(): Value of x is 900
Inside g(): Value of x is 500
Inside g(): Value of x is 500
our $x
行定义/声明整个包/代码体中可见的全局变量 $x
. 第一次调用 t()
全局变量 $x
被修改,此更改在全局可见 .
Perl与Python相反,默认情况下只为变量赋值,而Python默认定义一个新变量并在范围内为其赋值 . 这就是为什么我们在上面的Python和Perl代码中有不同的结果 .
这就是为什么即使在 t()
内调用 g()
也会打印出值500.在调用 t()
之后立即调用 g()
也会打印500并证明对 t()
的调用确实修改了全局范围内的全局变量 $x
. 函数 t()
中的 $x
是词法范围的,但不显示预期的行为,因为第8行的赋值对全局范围中的变量 $x
进行了全局更改 . 这导致在 t()
内调用 g()
显示500而不是30.在调用 h()
函数调用 g()
(第25行)时,函数 g()
打印2000,类似于函数 t()
的输出 . 但是当函数 h()
返回并且我们再次在它之后再次调用 g()
时,我们发现 $x
根本没有改变 . 这是 h()
内的 $x
的更改并未在其全局范围内更改 $x
,但仅在 h()
的范围内 . 对 $x
的更改在某种程度上暂时限制在使用 local
关键字的当前范围内 . 这是Perl实践中的动态范围 . 对 g()
的调用返回 g()
当前执行范围中变量 $x
的值,而不是 $x
的值,其中 g()
在代码a.k.a词法范围内定义 .
最后,在第28行调用函数 r()
时,关键字 my
强制创建一个新的词法范围的局部变量,与Python代码片段中函数 t()
中的行为相同 . 这与 h()
或 t()
中发生的事情形成鲜明对比,其中没有创建新的变量 . 在函数 r()
中,我们观察到对 g()
的调用实际上将 $x
的值打印为500,值 $x
在词法范围中已经定义了 g()
而不是当前执行范围中的值 g()
(与动态范围结果相反)在 h()
) . Perl函数 r()
是与原始Python函数 t()
的作用域行为最接近的匹配 .
默认情况下,Perl修改全局变量 $x
,而不是像在Python中那样创建一个新的词法范围的 $x
变量,这可能有时会成为Perl新手的混乱和错误的根源 . 对于静态类型语言,这不是问题,因为需要明确声明变量,并且不会出现是否正在分配现有变量或者正在定义和分配新变量的混淆的可能性 . 在动态类型语言中,这不需要显式声明,并且程序员不知道不适当地使用作用域语法的后果(如在Perl中使用 my
),如果不小心,它通常会导致意想不到的后果 . 程序员可能会认为在第8行声明了一个新变量,但实际上正在修改全局变量 $x
. 这正是Perl打算使用它的方式,但如果程序员不小心并且不完全了解它的含义,则会产生有趣的效果 . 在几百或几千行代码的大型程序中,这种错误很难捕获和调试 . 要记住的是,如果没有 my
前缀,Perl会将变量的赋值视为赋值而不是赋值赋值 .
默认情况下,Perl将块中的赋值视为对同名全局变量的赋值,并通过使用 my
来定义赋值给词法范围的局部变量,从而需要显式覆盖 . Python具有相反的默认行为,并且默认情况下将块中的所有赋值视为定义和分配给本地词法范围的变量 . 需要明确使用 global
关键字来覆盖此默认行为 . 我觉得Python 's default behavior is safer and maybe kinder to novice and intermediate level programmers than Perl'的默认作用域行为 .
请添加您可能知道的Perl和Python的任何其他微妙范围相关问题 .
1 回答
第二个Perl示例中的
$x = shift
行只是覆盖了一个全局的,词法范围的变量,就像将global x
添加到Python代码中一样 .这与动态范围无关,并且还有许多其他语言与Perl具有相同的行为 - 我认为Python是一个奇怪的人,因为要求显式导入在词法范围内可见的变量名 .
Perl代码的真正问题不是词法作用域,而是缺少声明的参数 . 使用声明的参数,将无法忘记
my
,问题也将消失 .我发现Python的范围界定方法(从Python 2开始)更有问题:它是不一致的(全局变量的显式导入以获得读写绑定,而嵌套函数中的词法自动绑定,但是只读)并使闭包成为可能 class 公民 .