[Tutor] Variable reference

Peter Otten __peter__ at web.de
Wed Jul 8 14:54:55 CEST 2015


Steven D'Aprano wrote:

> When writing a script or application, name management is not a big deal. 
> But in a library, temporary variables are pollution. They make it harder 
> for the users of your library to tell what's part of the API and what 
> isn't, and they make "from module import *" less useful. So if I have a 
> temporary global variable, I may want to get rid of it once I'm finished 
> with it. Here's a snippet from a library module I have, designed to be 
> used with * imports:
> 
>     tmp = set(C0.keys()) & set(C1.keys())
>     assert not tmp, 'duplicate control code acronyms: %s' % tmp
>     # Special check for SCG abbreviated acronym.
>     assert 'SGC' not in C0
>     assert 'SGC' not in C1
>     # Validate that the ^ and ESC codes are correct.
>     for C in (C0, C1):
>         for cc in C.values():
>             assert cc.code == _code(cc.ordinal), 'failed check: %s' % cc
>     del C, cc, tmp
> 
> Those three temporary variables, C, cc and tmp, would otherwise hang 
> around forever, polluting the namespace and confusing my module's users. 
> (My module's users, so far, is mostly me, but I'm easily confused.)

You are not alone with that design. The first thing that came up in the 
stdlib was the webbrowser module:

$ cd /usr/lib/python3.4
$ find . -name \*.py -print0 | xargs -0 egrep '\bdel\s+(_|[[:alnum:]])+$' | 
head -n1
./webbrowser.py:    del cmdline
$ 

Here's the relevant part of the code where the author manages to del two out 
of three helper variables:

"""
# OK, now that we know what the default preference orders for each
# platform are, allow user to override them with the BROWSER variable.
if "BROWSER" in os.environ:
    _userchoices = os.environ["BROWSER"].split(os.pathsep)
    _userchoices.reverse()

    # Treat choices in same way as if passed into get() but do register
    # and prepend to _tryorder
    for cmdline in _userchoices:
        if cmdline != '':
            cmd = _synthesize(cmdline, -1)
            if cmd[1] is None:
                register(cmdline, None, GenericBrowser(cmdline), -1)
    cmdline = None # to make del work if _userchoices was empty
    del cmdline
    del _userchoices
"""

I really don't like that approach; I prefer writing a helper function, 
properly marked as an implementation detail, e. g.

def _setup_userchoices(userchoices=None):
    """
    >>> _setup_userchoices(["one"])
    >>> _tryorder[0]
    'one'
    >>> _setup_userchoices(["two", "three"])
    >>> _tryorder[:3]
    ['two', 'three', 'one']
    """
    if userchoices is None:
        userchoices = os.environ.get("BROWSER", "").split(os.pathsep)
    for cmdline in reversed(userchoices):
        if cmdline != "":
            cmd = _synthesize(cmdline, -1)
            if cmd[1] is None:
                register(cmdline, None, GenericBrowser(cmdline), -1)

_setup_userchoices()

That is easy to test (if this were for real you'd use unit test, not 
doctest), and you don't have to decide whether the leftover `cmd` is kept 
intentionally or by accident, or be careful to avoid that you tread on names 
that *are* part of the API. 

That are the advantages for the author/reader of the module. As a user the 
extra _setup_userchoices() function doesn't bother me at all.




More information about the Tutor mailing list