Not possible to hide local variables

Chris Angelico rosuav at gmail.com
Wed Apr 29 09:36:33 CEST 2015


On Wed, Apr 29, 2015 at 4:25 PM, Cecil Westerhof <Cecil at decebal.nl> wrote:
> Op Tuesday 28 Apr 2015 10:06 CEST schreef Chris Angelico:
>> (note that I'm avoiding the multiple-argument syntax which doesn't
>> work in Python 3;
>
> I already did this with print. Are there other statements I have to
> take care for the possible problems with Python 3? I do not use Python
> 3, but that does not mean it is not a good idea to make my modules
> work with it.

As long as you can require a minimum Python version of 2.7 (usually
not hard these days; older Pythons are still around, but
diminishingly, and you can simply choose not to support them), it's
not too hard to make your code basically compatible. The biggest
change will be the bytes/unicode distinction; you can adorn your byte
strings with b"..." and your Unicode strings with u"...", but the hard
part is keeping track in your head of which one is which. For a lot of
programs, it's not difficult; ASCII strings fit happily into either
bytes or Unicode, so the bulk of your strings can be left unadorned.

>> if ParameterError is a subclass of ValueError you
>> could use that instead, but ValueError works)
>
> Was the wrong error: should have been TypeError. I find this strange:
> Python is compiled, but I get only an error when this statement is
> reached.

As Python code gets compiled, names get turned into either locals or
external lookups. You can inspect a function interactively:

>>> def checkme(x):
...     if x < 0: raise BotchedError("Oops")
...     return x + 1
...
>>> checkme.__code__.co_names
('BotchedError',)
>>> checkme.__code__.co_varnames
('x',)

Local variable are turned into special slots, matching co_varnames;
global name lookups are done at run time. This allows mutual
recursion:

def func1(x):
    if x % 2: return x - 1
    return func2(x + 2)

def func2(x):
    if x % 3: return x
    return func1(x - 4)

At compilation time, func1 has a global name reference that can't be
resolved... but by the time anyone gets to calling it, the module has
gained a func2, and all is well. If you want to check your code for
name mistakes, you could do something like this:

# checker.py
import sys
def check_module(module):
    mod = sys.modules[module]
    for attr in dir(mod):
        thing = getattr(mod, attr)
        try: names = thing.__code__.co_names
        except AttributeError: continue # Not a function
        for name in names:
            if name not in dir(mod) and name not in dir(mod.__builtins__):
                raise NameError("Function %s refers to %s which
doesn't exist" % (attr, name))

Then you just put this line at the bottom of your code to see if there
are any potential problems:

import checker; checker.check_module(__name__)

Alternatively, you could build functionality like this into a linter
or other code quality confirmation. For most code, this will be a safe
check; it is theoretically possible for a module to grow a new
attribute between the end of module execution and the actual calling
of a function (which is why the interpreter itself doesn't check like
this), but it's sufficiently unusual that you could safely disallow it
in your style guide or source control policy.

> I am of the school that it is good to check as much as possible.
> (Without going over the top.)

One of the key differences between the C++/Java mentality and the
Python mentality is that, while both schools of thought would broadly
agree with what you say here, we draw the line of "over the top" at
different places. In Java code, you're expected to type-check
everything; in Python code, just use it, and if the caller gives you
the wrong data type, that's his problem, not yours. In Java code,
exceptions are there to be carefully documented - "this function might
raise this/these exception(s)", and its callers have to decide at
compilation time whether they're going to handle them or let them
bubble. In Python code, unexpected exceptions are generally expected
to bubble all the way up to a generic handler - for most applications,
that means printing the exception to the console and terminating. You
deal with things you know how to deal with, and any weird things that
happen, again, not your problem. Neither philosophy is fundamentally
wrong, but other Java programmers will get very annoyed at you if you
adopt a Python style in Java code (using RuntimeError for everything,
and checking nothing), and your Python code will get unnecessarily
verbose and heavy if you adopt a Java style in Python code. It's up to
you to decide how worthwhile it is, but I recommend at least giving
the Python style a try (pun not intended); you may well like it, or
you may find that you want at least a bit more pre-checking, but
either way, you'll know why.

All the best!

ChrisA



More information about the Python-list mailing list