Python scoping issue with dictionary comprehension inside class level code
Asked Answered
R

1

6

Minimal example

class foo:
    loadings = dict(hi=1)
    if 'hi' in loadings:
        print(loadings['hi']) 
        # works
        print({e : loadings[e] for e in loadings})
        # NameError global name 'loadings' not defined

I tried referencing the class namespace as well but that isn't working either

class foo:
    loadings = dict(hi=1)
    if 'hi' in loadings:
        print(loadings['hi'])
        #works
        print({e : foo.loadings[e] for e in foo.loadings})
        #NameError: name 'foo' is not defined

And of course, this works as expected

class foo:
    loadings = dict(hi=1)
    if 'hi' in loadings:
        print(loadings['hi'])

print({e : foo.loadings[e] for e in foo.loadings})

I want to understand why this scope issue is happening then, if I am trying to do something insane understand the best way to do it otherwise. My feeling was that the first code snip should have worked as is, but of course it does not.

The goal

I am creating a DataManager class/module for some csv/json files along with canned database queries, a one stop shop for my program and obtaining data. There is some static data and some dynamic data so it seemed like a great use of static and non-static data members in the same class. While I understand that these could be module level variables, I like the concept of having static class data members (possibly because of a bias from Java). Any help is much appreciated

My solution (for now)

I ended up unfurling the list comprehension to stay in class scope, in the above it would become something like this

class foo:
    loadings = dict(hi=1)
    temp = dict()
    for e in loadings:
        temp[e] = loadings[e] # keep in mind this is a minimal example, I probably wouldn't do (just) this
    print(temp) # works
    del temp

It is not pretty but it works for now

Resent answered 1/4, 2014 at 14:36 Comment(3)
You should do all that inside an init function....Duvall
I think its also interesting to note that print({k: v for k, v in loadings.items()}) does work. So loadings is at least kinda visible in the dict comprehensionFemme
This was not done in the init function because this is static data for the class which is loaded once. I could have some flag like runonce or something but it seemed that class level code is there for a reason.Resent
N
4

Per the Name and Binding docs:

The scope of names defined in a class block is limited to the class block; it does not extend to the code blocks of methods – this includes comprehensions and generator expressions since they are implemented using a function scope. This means that the following will fail:

class A:
    a = 42
    b = list(a + i for i in range(10))

See this answer for more details.


In Python2, it is possible to use list comprehensions, since they were implemented without using a function scope:

dict([(e,loadings[e]) for e in loadings])

But this code would break if run in Python3. So here is an alternative workaround which would work in Python2 and Python3:

class Foo:
    def loadings():
        load = dict(hi=1)
        if 'hi' in load:
            print(load['hi']) 
            print({e:load[e] for e in load})
        return load
    loadings = loadings()

print(Foo.loadings)
Neurogram answered 1/4, 2014 at 14:59 Comment(1)
Thank you very much, for a clear and concise explanation! I wanted to point out to you that my intention would be to replace the last two lines of your last code block with something like print(Foo.loadings) and use it as a static data member.Resent

© 2022 - 2024 — McMap. All rights reserved.