gotchas to be aware of

While investigating scoping in Perl and Python , I came across a silent scoping related behavior of Perl that can cause bugs very difficult to trace. Specifically for programmers who are new to the language and not fully aware of all it's nuances. I have provided example code for both Perl and Python to illustrate how scoping works in both the languages

In Python if we run the code:

    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)

The resulting output is:

Inside t(): Value of x is 200
Inside t()-tt(): Value of x is 200
Inside g(): Value of x is 30

This is the usual lexical scoping behavior. Python treats an assignment in a block by default as the definition and assignment to a new variable, not to the global variable that may exist in the enclosing scope. To override this behavior, the keyword global needs to be used explicitly to modify the global variable x instead. The scope of the variable x in the function g() is determined by the place in the program where it is defined, not where the function g() gets called. As a result of lexical scoping behavior in Python when the function g() is called within the function t() where another lexically scoped variable x is also defined and set to the value of 200, g() still displays the old value 30, as that is the value of the variable x in scope where g() was defined. The function tt() displays a value of 200 for the variable x that is in the the lexical scope of tt() . Python has only lexical scoping and that is the default behavior.

By contrast Perl provides the flexibility of using lexical scoping as well as dynamic scoping. Which can be a boon in some cases, but can also lead to hard to find bugs if the programmer is not careful and understands how scoping works in Perl.

To illustrate this subtle behavior, if we execute the following Perl code:

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();

the resulting output is:

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

The line our $x defines/declares a global variable $x visible throughout the Package/code body. The first call to t() the global variable $x is modified and this change is visible globally.
Perl as opposed to Python by default just assigns a value to a variable, whereas Python by default defines a new variable and assigns a value to it within a scope. That is why we have the different results in the Python and the Perl code above.
That is why even the call to g() within t() prints the value 500. The call to g() immediately after the call to t() also prints 500 and proves that the call to t() indeed modified the global variable $x in the global scope. $x within function t() is lexically scoped, but not displaying the expected behavior as the assignment at line 8 makes a global change to the variable $x in the global scope. This results in the call to g() within t() displaying 500 instead of 30. In the call to the function h() where g() is called (line 25), the function g() prints 2000, similar to the output from function t() . However when the function h() returns and we again call g() immediately after it, we find that $x has not changed at all. That is the change to $x within h() did not change $x in it's global scope but only within the scope of h() . The change to $x is somehow temporarily confined to within the current scope where the local keyword is used. This is dynamic scoping in practice in Perl. The call to g() returns the value of the variable $x in the current execution scope of g() instead of the value of $x where g() is defined within the code aka lexical scope.

Finally in the call to function r() at line 28, the keyword my forces the creation of a new lexically scoped local variable, identical to the behavior within function t() in the Python code snippet. This is in stark contrast to what happened within h() or t() where no new variable was ever created. Within function r() we observe that the call to g() actually prints the value of $x as 500, the value $x has in the lexical scope where g() has been defined and not the value in the current execution scope of g() (as opposed to dynamic scope result in h() ). The Perl function r() is the closest match in terms of scoping behavior to the original Python function t() .

By default Perl modifies the global variable $x instead of creating a new lexically scoped $x variable as in Python, and this can be some times a source of confusion and errors to a Perl newbie. For statically typed languages, this is not an issue as variables need to be declared explicitly and the chances of any confusion of whether an existing variable is being assigned to or a new variable is being defined and assigned to does not arise. In dynamically typed languages, that do not require explicit declaration and where the programmer is unaware of the consequences of not using scoping syntaxes appropriately (as in use of my in Perl), it can often lead to unintended consequences if one is not careful. The programmer might think that a new variable is being declared at line 8, but actually the global variable $x is being modified. This is exactly the way Perl intends it to be used, but can lead to interesting effects if the programmer is not careful and not fully aware of what it means. This kind of error could get difficult to catch and debug in a large program of several hundreds or thousands of lines of code. The thing to remember is that without a my prefix, Perl treats assignments to variables as just assignments not a definition + assignment.

Perl treats assignment in a block by default as assignment to the global variable of the same name and requires explicit override by using my to define + assign to a lexically scoped local variable. Python has opposite default behavior and treats all assignments in a block by default as defining and assigning to a local lexically scoped variable. The use of the global key word needs to be explicitly used to override this default behavior. I feel Python's default behavior is safer and maybe kinder to novice and intermediate level programmers than Perl's default scoping behavior.

Please add any other subtle scoping related issues to be aware of for both Perl and Python that you may be aware of.


The line $x = shift in your second Perl example just overwrites a global, lexically-scoped variable, same as if you would add global x to your Python code.

This has nothing to do with dynamic scoping, and there are many other languages with the same behaviour as Perl - I'd consider Python the odd man out here for requiring to explicitly import a variable name visible at lexical scope.

The real problem with the Perl code is not lexical scoping, but lack of declared parameters. With declared parameters, it would be impossible to forget the my , and the problem would go away as well.

I find Python's approach to scoping (as of Python 2) far more problematic: it's inconsistent (explicit imports for globals to get a read-write binding, whereas lexicals in nested functions are bound automatically, but read-only) and makes closures secon-class citizens.

链接地址: http://www.djcxy.com/p/4884.html

上一篇: 参数的默认值

下一篇: 需要注意的问题