需要注意的问题
在调查Perl和Python的范围界定时 ,我遇到了一个与Perl无关的与范围有关的行为,可能会导致很难跟踪的bug。 特别针对那些刚接触这门语言的程序员,并没有完全意识到它的细微差别。 我已经提供了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()
被调用的位置。 当函数g()
在函数t()
中调用时,其中另一个词法作用域变量x
也被定义并设置为值200,因此Python中的词汇作用域行为的结果是g()
仍旧显示旧值30,因为这是定义g()
范围内的变量x的值。 函数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
在整个Package /代码体中可见。 对t()
全局变量$x
的第一次调用被修改,并且全局变化可见。
默认情况下,Perl与Python相对,只是为变量赋值,而Python默认定义了一个新变量并在一个范围内为其赋值。 这就是为什么我们在上面的Python和Perl代码中有不同的结果。
这就是为什么即使对t()
g()
内的g()
的调用打印值500.在调用t()
之后立即调用g()
t()
也会打印500并证明对t()
的调用确实修改了全局变量$x
在全球范围内。 函数t()
$x
是词汇范围的,但不会显示预期的行为,因为第8行的分配会全局更改全局范围中的变量$x
。 这导致在t()
调用g()
显示500而不是30.在调用函数h()
其中g()
被调用(第25行)时,函数g()
打印2000,类似于函数t()
输出。 然而,当函数h()
返回并且我们再次在它后面调用g()
时,我们发现$x
根本没有改变。 这是改变$x
中h()
并没有改变$x
在它的全球范围内,但只有范围内h()
对$x
的更改暂时限制在使用local
关键字的当前范围内。 这是Perl中的动态范围实践。 到呼叫g()
返回变量的值$x
中的当前执行范围g()
代替的值$x
其中g()
是代码又名词法范围内定义。
最后,在第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的默认行为比Perl的默认范围行为更安全,对新手和中级程序员也更友善。
请添加任何其他细微的范围相关问题,以了解您可能知道的Perl和Python。
第二个Perl示例中的$x = shift
行仅覆盖全局的词法范围变量,就像您将global x
添加到您的Python代码一样。
这与动态范围无关,还有许多与Perl具有相同行为的其他语言 - 我认为Python是奇怪的人,因为它需要显式地导入在词法范围中可见的变量名。
Perl代码的真正问题不是词法范围,而是缺少声明的参数。 使用已声明的参数,将不可能忘记my
的问题,问题也会消失。
我发现Python的范围确定方法(从Python 2开始)的问题更为严重:它不一致(全局变量的显式导入以获得读写绑定,而嵌套函数中的词汇自动绑定,但是只读)班级公民。
链接地址: http://www.djcxy.com/p/4883.html