0
class test(object):
    data = 1
    dataSet = tuple(data for i in range(2))

This snippet of code is the simplest way to get a NameError with a <genexpr>.

Thanks to Jim, I know the simplest way to fix it is to simply use a lambda :

class test(object):
    data = 1
    dataSet = (lambda d = data: tuple(d for i in range(2)))()

I know I could also decorate the class to modify make the tuple when the class object is fully created.

def tuple_data(cls):
    setattr(cls, 'dataSet', tuple(getattr(cls, 'dataSet')))
    return cls

and now:

@tuple_data
class test(object):
    data =  1
    dataSet = (test.data for i in range(2))

I'm mainly trying to understand the computing behind the scenes.

By Jim's answer, the <genexpr> cannot access the class data since it doesn't exist when the <genexpr> is evaluated. By this answer, the <genexpr> just doesn't have access to the class' namespace, only to its own local scope. Problem with this is a <genexpr> uses LOAD_GLOBAL which is rather confusing.

On top of that, Jim's solutions are working but I cannot understand how exactly. I guess the decorator solutions works simply because the decorator modify the class functionnality either once it is fully created or when it is called to be instantiated. On the other hand, I cannot understand why the lambda is working.

Community
  • 1
  • 1
Alexandre Parent
  • 127
  • 1
  • 10

1 Answers1

2

You could get this to work by not using a comprehension and instead multiplying a single element list by the amount you need to initialize the tuple:

data = tuple([dataSet] * 2)

Alternatively, for highlighting how ugly but interesting lambdas can be, passing it as an argument to a lambda function and calling it:

data = (lambda dataSet=dataSet: tuple(dataSet for i in range(2)))()

should do the trick.


As for the reasoning behind this, I cannot seem to find any hard documentation, PEP 289 simply states that users are encouraged to create generator expressions only inside functions and fall-back to function generators for more complex scenarios. It just seems to be something that just is that way.

It is apparently compiled this way. I went ahead and examined the code object for a class that uses a generator expression to see what was going on, first, define a class:

cls = """
class test(object):
    i = 10
    d = tuple(i for j in range(2))
"""

Then, compile it:

cls_code = compile(cls, "stdin", mode = 'exec')

and now, we disassemble it and examine the contents of the class definition which is located in cls_code.co_consts[0]:

import dis
dis.dis(cls_code.co_consts[0])
  2           0 LOAD_NAME                0 (__name__)
              3 STORE_NAME               1 (__module__)
              6 LOAD_CONST               0 ('test')
              9 STORE_NAME               2 (__qualname__)

  3          12 LOAD_CONST               1 (10)
             15 STORE_NAME               3 (i)

  4          18 LOAD_NAME                4 (tuple)
             21 LOAD_CONST               2 (<code object <genexpr> at 0x7ff3e08584b0, file "stdin", line 4>)
             24 LOAD_CONST               3 ('test.<genexpr>')
             27 MAKE_FUNCTION            0
             30 LOAD_NAME                5 (range)
             33 LOAD_CONST               4 (2)
             36 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             39 GET_ITER
             40 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             43 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             46 STORE_NAME               6 (d)
             49 LOAD_CONST               5 (None)
             52 RETURN_VALUE

We see that inside the class, loading and storing is done via the LOAD_NAME opcode (which uses the co_names tuple which contains the names used inside the code object).

The generator expression, on the other hand, loads names differently, the code object for the generator expression is located in in cls_code.co_consts[0].co_consts[2] (it is a constant in the code object for the class definition, if we disassemble it, we can see that it uses LOAD_GLOBAL when it tries to look up i:

dis.dis(cls_code.co_consts[0].co_consts[2])
  4           0 LOAD_FAST                0 (.0)
        >>    3 FOR_ITER                11 (to 17)
              6 STORE_FAST               1 (j)
              9 LOAD_GLOBAL              0 (i)
             12 YIELD_VALUE
             13 POP_TOP
             14 JUMP_ABSOLUTE            3
        >>   17 LOAD_CONST               0 (None)
             20 RETURN_VALUE

Another way around this is by noticing that you're actually 'executing' the generator expression because you wrapped it in a tuple call, since it is executing before the class has been created, a reference of the form class_name.attr will fail.

If you defined your class without the tuple call, you could get it to work like this:

class test(object):
    dataSet = damage("ranged", "energy", 1, 0, 0.5, 0, 0, -0.5, 0,    0, False)
    data = (test.dataSet for i in range(2)

And then, after the class has been created, you can now wrap it in a tuple call and the look up for test.dataSet will succeed:

test.data = tuple(test.data)

If this is something you don't want to do manually, you could always create a decorator to do it for you automatically:

def tuple_data(cls):
    setattr(cls, 'data', tuple(getattr(cls, 'data')))
    return cls

and now:

@tuple_data
class test(object):
    dataSet = damage("ranged", "energy", 1, 0, 0.5, 0, 0, -0.5, 0,    0, False)
    data = (test.dataSet for i in range(2)

Will pass data to tuple after the class test has been created.

Dimitris Fasarakis Hilliard
  • 150,925
  • 31
  • 268
  • 253
  • Interresting... But I'm also interrested in the reason why my code is broken.. And the rationnale behind creating genexpr in another scope than where it was called and needed. – Alexandre Parent Jul 06 '16 at 16:25
  • But your second solution does work.... All hail lambdas... – Alexandre Parent Jul 06 '16 at 16:30
  • 1
    @AlexandreParent yes, I deleted and re-edited my answer. It seems I had already defined a class named `test` when I was trying out the first solution (I removed it now). The rest should work as you said. – Dimitris Fasarakis Hilliard Jul 06 '16 at 16:39
  • 1
    I'll extend my answer with a description of why this happens when I find the appropriate sources. – Dimitris Fasarakis Hilliard Jul 06 '16 at 16:39
  • @AlexandreParent I updated my answer, I also added another way to go around this with the use of a class decorator. – Dimitris Fasarakis Hilliard Jul 06 '16 at 18:57
  • If I understand the data model properly, class exist when they are defined. The definiton makes the object... What you're doing with your decorator is modifying `test.data` during the instanciation. And your supposition on why it fails, "it is executing before the class has been created, a reference of the form `class_name.attr` will fail," is simply impossible. You can easily call any class_attr without instantiating the class. Calling `class_attr` only ever failed me inside ``.... An explication for this behaviour?? Why would `class_attr` lookup fails for only? – Alexandre Parent Jul 07 '16 at 21:02
  • From [here](http://stackoverflow.com/questions/11669379/undefined-global-in-list-generator-expression-using-python3-works-with-python2), it seems problem is simply inner scope unable to get outer scope variables. Which is quite normal..... And that's likely the difference between the two codes.`LOAD_NAME` loads names in the current namespace while `LOAD_GLOBAL` would loads names from the current scope. – Alexandre Parent Jul 07 '16 at 21:09
  • 1
    @AlexandreParent statements in the top level of the class definition cannot reference the `class` name, because the `class` object has not been defined yet. The `class` object is created after all the statements in the definition have been executed, not during the definition. `LOAD_NAME` uses the `co_names` (which most likely represents the namespace) and is already present during the execution of the `class` definition while `LOAD_GLOBAL` loads from the `global` scope. – Dimitris Fasarakis Hilliard Jul 08 '16 at 02:43
  • Ok... That's interresting. So if I understand what you're saying. During an object creation, every statements inside it are ran then the class calls its metaclass `__new__`? Or are these statements executed during the `__new__`??? – Alexandre Parent Jul 09 '16 at 17:33
  • Nice answer, but you appear to have reversed the roles of `data` and `dataset` compared to what's in the OP's code. – PM 2Ring Jul 10 '16 at 04:41
  • @PM 2Ring His code was right.. I just edited the question so it is clearer what I ask.. if you read the question, I included Jim's ways to fix it.... – Alexandre Parent Jul 11 '16 at 16:07
  • @AlexandreParent: Ah, I see. :) It was only a minor thing, and I up-voted Jim's answer anyway. – PM 2Ring Jul 11 '16 at 16:25