osdir.com


[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Strange Class definition


ast wrote:

> Hello
> 
> Following syntax doesn't generate any errors:
> 
>  >>> foo=0
>  >>> Class Foo:
>         foo
> 
> But class Foo seems empty
> 
> Is it equivalent to ?
> 
>  >>> class Foo:
>          pass

The resulting class is equivalent, but the expression `foo` is actually 
evaluated during class creation. You can easily see this when you replace 
foo with a print call, say:

>>> for foo in "first", "second":
...     class A: print(foo)
... 
first
second

If you compare the byte code you may note a small difference to def f(): foo

>>> import dis
>>> c = compile("class A: foo", "<nofile", "exec")
>>> dis.dis(c)
  1           0 LOAD_BUILD_CLASS
              1 LOAD_CONST               0 (<code object A at 
0x7f9cce602ae0, file "<nofile", line 1>)
              4 LOAD_CONST               1 ('A')
              7 MAKE_FUNCTION            0
             10 LOAD_CONST               1 ('A')
             13 CALL_FUNCTION            2 (2 positional, 0 keyword pair)
             16 STORE_NAME               0 (A)
             19 LOAD_CONST               2 (None)
             22 RETURN_VALUE
>>> c.co_consts[0]
<code object A at 0x7f9cce602ae0, file "<nofile", line 1>
>>> dis.dis(c.co_consts[0])
  1           0 LOAD_NAME                0 (__name__)
              3 STORE_NAME               1 (__module__)
              6 LOAD_CONST               0 ('A')
              9 STORE_NAME               2 (__qualname__)

The relevant instruction:

             12 LOAD_NAME                3 (foo)
             15 POP_TOP
             16 LOAD_CONST               1 (None)
             19 RETURN_VALUE

For comparison a function:

>>> def f(): foo
... 
>>> dis.dis(f)
  1           0 LOAD_GLOBAL              0 (foo)
              3 POP_TOP
              4 LOAD_CONST               0 (None)
              7 RETURN_VALUE

In the class body LOAD_NAME is used instead of LOAD_GLOBAL. This allows 
dynamical scoping

>>> foo = "outer"
>>> class A:
...     print(foo)
...     foo = "inner"
...     print(foo)
... 
outer
inner
>>> A.foo
'inner'

whereas in the function you get an error:

>>> def f():
...     print(foo)
...     foo = "inner"
...     print(foo)
... 
>>> f()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in f
UnboundLocalError: local variable 'foo' referenced before assignment