Can global variable be passed into Python function?

Steven D'Aprano steve+comp.lang.python at pearwood.info
Fri Feb 28 18:33:31 EST 2014


On Fri, 28 Feb 2014 21:20:52 +0200, Marko Rauhamaa wrote:

> Your example:
[snip]

I'm not going to show the example as quoted by you, because my news 
client is wrapping it in ugly ways and I don't want to spend the time 
fixing it. I'll just say that, as given, it's a great big wall of text, a 
solid and ugly block, but can be easily fixed to be more readable and 
look more like a switch with a judicious amount of indentation:

    compare_key = {
        ast.Assign: 
            # Same target(s).
            lambda node: ' '.join(dump(t) for t in node.targets),
        ast.AugAssign: 
            # Same target and same operator.
            lambda node: dump(node.target) + dump(node.op) + "=",
        ast.Return: 
            # A return statement is always compatible with another.
            lambda node: "(easy)",
        ast.Expr: 
            # Calling these never compatible is wrong. Calling them
            # always compatible will give lots of false positives.
            lambda node: "(maybe)",
        }

which compares favourably with your version:


>     with key from ast:
>         if Assign:
>             return ' '.join(dump(t) for t in node.targets)
>         elif AugAssign:
>             # Same target and same operator.
>             return dump(node.target) + dump(node.op) + "="
>         elif Return:
>             # A return statement is always compatible with another.
>             return "(easy)"
>         elif Expr:
>             # Calling these never compatible is wrong. Calling them 
>             # always compatible will give lots of false positives.
>             return "(maybe)"


I've deliberately dropped out  "else" clause, because the two code 
snippets as shown by you don't do the same thing. The dict dispatch table 
shown initially lists the remaining keys allowed:

    ast.Import, ast.ImportFrom, ast.Pass, ast.Raise, ast.If

and sets them all individually to five different but equivalent 
functions, lambda node: float("nan"). This is both good and bad: good, 
because it enumerates precisely what keys are expected, and will fail if 
given an unexpected value; bad because it requires repeating yourself 
five times to give the five keys the same function:

    ast.Import:
        lambda node: float("nan"),
    ast.ImportFrom:
        lambda node: float("nan"),

etc. (Actually five different functions which happen to do exactly the 
same thing.) On the other hand, your with...if...elif version simply uses 
a single "else" statement, which means it will accept *any other value at 
all*, not just the five keys accepted by Chris' version. So there is a 
semantic difference between the two.

In practice, sometimes you want a strict dispatch table that raises an 
error when given something unexpected, other times nothing is unexpected 
and you want an "else" or "otherwise" clause that captures everything. 
There are all sorts of ways to handle this in practice, e.g. using 
defaultdict, or subclassing dict and giving it a __missing__ method, or 
simply deal with the default case outside of the table itself:

    # instead of this
    dispatch[key](node)

    # do this
    dispatch.get(key, lambda node: float('nan'))(node)


> Which do *you* find more readable?

With proper code layout, the dict-based dispatch table is at least as 
readable, and it avoids needing any magic compiler support.



-- 
Steven



More information about the Python-list mailing list