4

I would like doing:

class Whatevs(object):
    foo = 3
    bar = foo * 3

    def __init__(self):
        # enhance!

or How acan I make it possible to work correctly. ;)

EDIT:

Ok, it turns out that that is not the problem, the above works fine, this though:

class Whatevs(object):
    foo = 3
    zap = list(foo for _ in range(10))

Oh you, Python! I can come up with some workarounds, but can someone explain to me what is going on precisely? Why can't the generator access the class variables?

noio
  • 5,744
  • 7
  • 44
  • 61

3 Answers3

4

A generator comprehension is actually shorthand for defining a generator explicitly. Behind the scenes what you are trying to write is equivalent to:

class Whatevs(object):
    foo = 3
    def _gen():
        for _ in range(10):
            yield foo
    zap = list(_gen())

Unfortunately you cannot access scoped variables like foo from inside a generator or a function defined in the class body (and you can't use Whatevs.foo because Whatevs doesn't exist yet.

One option is to move the code outside the class body:

class Whatevs(object):
    foo = 3

Whatevs.zap = list(Whatevs.foo for _ in range(10))

Another option is to turn foo into a parameter:

def _init_zap(foo):
    return list(foo for _ in range(10))

class Whatevs(object):
    foo = 3
    zaps = _init_zap(foo)

In Python 2.x a list comprehension works here, but one of the changes for Python 3.x was that list comprehensions also use a separate scope (i.e. a hidden function) just like generator comprehensions, so a list comprehension will also break in Python 3.

Duncan
  • 92,073
  • 11
  • 122
  • 156
  • Oh! Thanks for that info, especially the last bit. I was indeed weirded out by the inconsistency between list comprehension and generator! I'm glad that they at least broke all of it in Python 3! ;) j/k. – noio Feb 13 '13 at 15:28
  • @Noio: Yeah, this and other "improvements" made in Python 3 are enough to make one wonder if the adage "A foolish consistency is the hobgoblin of little minds" might be applicable. – martineau Feb 13 '13 at 16:27
1

You could try:

class Whatevs(object):
    foo = 3
    zap = [foo] * 10

or even:

class Whatevs(object):
    foo = 3
    zap = [foo for _ in range(10)]

The reason is that classes in Python have their own namespace. Scopes introduced into this namespace do not have access to this namespace, thus, from the scope of your generator expression, you cannot access the variables inside Whatevs' namespace.

My list comprehension doesn't introduce a new scope - at least in Python 2.x. In Python 3.x, this has changed, so there also my option 2 wouldn't work.

Thorsten Kranz
  • 12,492
  • 2
  • 39
  • 56
1

A general solution is to wrap in a lambda function, works the same way in Python2 and Python3

class Whatevs(object):
    foo = 3
    zap = (lambda foo=foo:list(foo for _ in range(10)))()
John La Rooy
  • 295,403
  • 53
  • 369
  • 502