简体   繁体   中英

Is there an equivalent in Python of Fortran's “implicit none”?

In Fortran there is a statement Implicit none that throws a compilation error when a local variable is not declared but used. I understand that Python is a dynamically typed language and the scope of a variable may be determined at runtime.

But I would like to avoid certain unintended errors that happen when I forget to initialize a local variable but use it in the main code. For example, the variable x in the following code is global even though I did not intend that:

def test():
    y=x+2  # intended this x to be a local variable but forgot
           # x was not initialized 
    print y


x=3
test() 

So my question is that: Is there any way to ensure all variables used in test() are local to it and that there are no side effects. I am using Python 2.7.x. In case there is a local variable, an error is printed.

So my question is that: Is there any way to ensure all variables used in test() are local to it and that there are no side effects.

There is a technique to validate that globals aren't accessed.

Here's a decorator that scans a function's opcodes for a LOAD_GLOBAL .

import dis, sys, re, StringIO

def check_external(func):
    'Validate that a function does not have global lookups'
    saved_stdout = sys.stdout
    sys.stdout = f = StringIO.StringIO()
    try:
        dis.dis(func)
        result = f.getvalue()
    finally:
        sys.stdout = saved_stdout
    externals = re.findall('^.*LOAD_GLOBAL.*$', result, re.MULTILINE)
    if externals:
        raise RuntimeError('Found globals: %r', externals)
    return func

@check_external
def test():
    y=x+2  # intended this x to be a local variable but forgot
           # x was not initialized
    print y

To make this practical, you will want a stop list of acceptable global references (ie modules). The technique can be extended to cover other opcodes such as STORE_GLOBAL and DELETE_GLOBAL .

All that said, I don't see straight-forward way to detect side-effects.

There is no implicit None in the sense you mean. Assignment will create a new variable, thus a typo might introduce a new name into your scope.

One way to get the effect you want is to use the following ugly-ish hack:

def no_globals(func):
    if func.func_code.co_names:
        raise TypeError(
            'Function "%s" uses the following globals: %s' % 
            (func.__name__, ', '.join(func.func_code.co_names)))
    return func

So when you declare your function test –with the no_globals wrapper–you'll get an error, like so:

>>> @no_globals
... def test():
...     y = x + 2  # intended this x to be a local variable but forgot
...                # x was not initialized 
...     print y
... 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in no_globals
TypeError: Function "test" uses the following globals: x
>>> 
>>> x = 3
>>> test() 
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'test' is not defined

Just avoid using globally-scoped variables at all. And if you must, prefix their names with something you'll never use in a local variable name.

In Python, this is quite simply entirely legal. In fact, it is a strength of the language! This (lack) of error is the reason why you can do something like this:

def function1():
    # stuff here
    function2()

def function2():
    pass

Whereas in C, you would need to "forward declare" function2 .

There are static syntax checkers (like flake8 ) for Python that do plenty of work to catch errors and bad style, but this is not an error, and it is not caught by such a checker. Otherwise, something like this would be an error:

FILENAME = '/path/to/file'
HOSTNAME = 'example.com'

def main():
    with open(FILENAME) as f:
        f.write(HOSTNAME)

Or, something even more basic like this would be an error:

import sys
def main():
    sys.stdout.write('blah')

The best thing you can do is use a different naming convention (like ALL_CAPS) for module level variable declarations. Also, make it a habit to put all of your code within a function (no module-level logic) in order to prevent variables from leaking into the global namespace.

If you were really worried about this, you could try the following:

def test():
    try:
        x
    except:
        pass
    else:
        return
    y = x+2
    print y

But I'd recommend simply being mindful when writing a function that you don't try to reference things before assigning them. If possible, try to test each function separately, with a variety of carefully-defined inputs and intended outputs. There are a variety of testing suites and strategies , not to mention the simple assert keyword.

Is there any way to ensure all variables used in test() are local to it and that there are no side effects.

No. The language offers no such functionality.

There is the built in locals() function. So you could write:

y = locals()['x'] + 2

but I cannot imagine anyone considering that to be an improvement.

To make sure the correct variable is used, you need to limit the scope of the lookup. Inside a function, Python will look to arguments defined in line, then to the args and kwargs . After those, its going to look outside the function. This can cause annoying bugs if the function depends on a global variable that gets changed elsewhere.

To avoid using a global variable by accident, you can define the function with a keyword argument for the variables your going to use:

def test(x=None):
    y=x+2  # intended this x to be a local variable but forgot
           # x was not initialized 
    print y

x=3
test() 

I'm guessing you don't want to do this for lots of variables. However, it will stop the function from using globals.

Actually, even if you want to use a global variable in the function , I think its best to make it explicit:

x = 2
def test(x=x):
    y=x+2  # intended this x to be a local variable but forgot
           # x was not initialized 
    print y
x=3
test() 

This example will use x=2 for the function no matter what happens to the global value of x afterwards. Inside the function, x is fixed to the value it had at compile time.

I started passing global variables as keyword arguments after getting burned a couple times. I think this is generally considered good practice?

The offered solutions are interesting, especially the one using dis.dis , but you are really thinking in the wrong direction. You don't want to write such a cumbersome code.

  • Are you afraid that you will reach a global accidentally? Then don't write globals. The purpose of module globals is mostly to be reached. (in a comment I have read that you have 50 globals in scope, which seems to me that you have some design errors).

  • If you still DO have to have globals, then either use a naming convention (UPPER_CASE is recommended for constants, which could cover your cases).

  • If a naming convention is not an option either, just put the functions you don't want to reach any global in a separate module, and do not define globals there. For instance, define pure_funcs and inside of that module, write your "pure" functions there, and then import this module. Since python has lexical scope, functions can only reach variables defined in outer scopes of the module they were written (and locals or built-ins, of course). Something like this:

     # Define no globals here, just the functions (which are globals btw) def pure1(arg1, arg2): print x # This will raise an error, no way you can mix things up. 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM