dangerous class neighborhood

Chris Angelico rosuav at gmail.com
Thu Dec 27 17:10:42 EST 2018

On Fri, Dec 28, 2018 at 8:47 AM Avi Gross <avigross at verizon.net> wrote:
> Question 2: Do you want the variables available at the class level or at the
> instance level?

For constants, definitely put them on the class. They'll be available
on instances as well ("for free", if you like). For mutables,
obviously you need to decide on a case-by-case basis.

> Question 3: Which python variations on syntactic sugar, such as list
> comprehensions, get expanded invisibly in ways that make the problem happen
> by asking for variables to be found when no longer in the visible range?

The oddities with comprehensions were tackled partly during the
discussion of PEP 572. If you want to know exactly why this isn't
changing, go read a few hundred emails on the subject. A lot of the
main points are summarized in the PEP itself:


> There may be matters of efficiency some would consider but some of the
> examples seen recently seemed almost silly and easy to compute. The people
> asking about this issue wanted to define a bunch of CONSTANTS, or things
> that might as well be constants, like this:
> def Foo():
>                 A = ("male", "female", "other")
>                 B = [ kind[0] for kind in A ]            # First letters
> only
>                 # And so on making more constants like a dictionary mapping
> each string to a number or vice versa.
> All the above can be evaluated at the time the class is defined but
> unintuitive scope rules make some operations fail as variables defined in
> the scope become unavailable to other things that SEEM to be embedded in the
> same scope.

If you write simple and Pythonic code, these will almost always work
perfectly. The recent thread citing an oddity worked just fine until
it was written to iterate over range(len(x)) instead of iterating

> If they are ONLY to be used within an instance of Foo or invoked from within
> there, there may be a fairly simple suggestion. If you already have a
> __init__ method, then instantiate the variables there carefully using the
> self object to reference those needed.

But why? __init__ should initialize an instance, not class-level
constants. A Python class is not restricted to just methods, and
there's no reason to avoid class attributes.

> Create a function either outside the class or defined within. Have it do any
> internal calculations you need in which all internal variables can play
> nicely with each other. Then let it return all the variables in a tuple like
> this:
> def make_sexual_constants():
>                 A = .
>                 B = .
>                 C = f(A,B)
>                 D = .
> def Foo():
>                 (A, B, C, D) = make_sexual_constants():

Lovely. Now you have to define your variables once inside the
function, then name them a second time in that function's return
statement, and finally name them all a *third* time in the class
statement (at least, I presume "def Foo():" is meant to be "class
Foo:"). A mismatch will create bizarre and hard-to-debug problems.
What do you actually gain? Can you show me real-world code that would
truly benefit from this?

> Can we agree that the class Foo now has those 4 variables defined and
> available at either the class level or sub-class or instance levels? But the
> values are created, again, in a unified safe environment?

Unified? No more so than the class statement itself. Safe? Definitely
not, because of the mandatory duplication of names.

> As noted in section 3, it would be good to know what python features may be
> unsafe in this kind of context. I had an unrelated recent discussion where
> it was mentioned that some proposed feature changes might not be thread
> safe. Valid consideration when that may lead to hard-to-explain anomalies.

Uhh..... nope, that's nothing but FUD. There is no reason to believe
that some language features would be "unsafe".

> We now hear that because a list comprehension can be unwound internally into
> a "while" loop and an "if" statement and that some parts may expand to calls
> to a "range" statement, perhaps some variables are now in more deeply
> embedded contexts that have no access to any class variables.

No idea what you're looking at. A comprehension can be unwound in a
fairly straight-forward way, although there are some subtleties to

B = [ kind[0] for kind in A ]
# equivalent to, approximately:
def listcomp(iter):
    result = []
    for kind in iter:
    return result
B = listcomp(A)

For casual usage, you can describe a list comp very simply and neatly:

B = [ kind[0] for kind in A ]
# equivalent to, more approximately:
B = []
for kind in A:

Nothing here expands to a call to range(), nothing has a while loop.
The only way you'll get an "if" is if you had one in the comprehension

> I think that
> is quite reasonable; hence my suggestion we need to know which ones to
> avoid, or use a workaround like expanding it out ourselves and perhaps
> carefully import variables into other contexts such as by passing the
> variable into the function that otherwise cannot access it from a point it
> can still be seen.

Sure. If the comprehension doesn't work for you, just put a for loop
inside your class statement. This is not a problem.

> Least, but at least last, I ask if the need really exists for these
> variables as constants versus functions. If creating C this way runs into
> problems, but A and B are fine, consider making a method with some name like
> Foo.get_C() that can see A and B and do the calculation and yet return the
> value of C needed. Less efficient but .

Definitely not. That would imply that the value of C might change, or
might have significant cost, or in some other way actually merits a
getter function. Python isn't built to encourage that.

Class scope has edge cases, to be sure, but they're much more notable
in carefully-crafted exploratory code than in actual real-world code.


More information about the Python-list mailing list