[BangPypers] wierd class behavior

Abdul Muneer abdulmuneer at gmail.com
Sat Jan 12 20:26:40 CET 2013


Thank you Anand for bringing up this thought provoking question!
Regardless of the possible explanations, it has to be said that this
behaviour is indeed confusing. This will conflict with the general idea of
LEGB scoping order (i.e. Local, Enclosing Function local, Global,
Built-in). The functions f and g apparently show conflicting behavior in
the way variables x and y are treated respectively.

To summarize the confusions:

   1. in *f*, print(x) statement under class definition causes an
   exception. Shouldn't it have accepted the *x* that was declared in its
   Enclosing function?
    2. If we accept that as the default behaviour, why on earth is the *gety
   * function inside *g* returning value 1 instead of 2? (It would have
   returned 1 regardless of its class had a statement like 'y=1'!)

The following might explain this:

The original variable '*y*' in function *g* and the '*y*' inside
*gety*function are the same object! But the '
*y*' in the class Foo local is different. You can check it this way:

def g():
    y = 1
    *print 'id(y) originally in g:', id(y)*
    class Foo():
        y = 2
        *print 'id(y) in class Foo:', id(y)*
        def gety(self):
            *print 'id(y) in function gety inside Foo:', id(y)*
            return y
    foo = Foo()
    print (y, foo, foo.gety())

But why??
*
*
*The x in function f and y in function g, though may look identical to us,
are stored differently by python! *This can be seen from the disassembly of
both functions:

>>> dis(f)
  2           0 LOAD_CONST               1 (1)
              3 STORE_FAST               0 (x)

  3           6 LOAD_CONST               2 ('Foo')
              9 LOAD_CONST               4 (())
             12 LOAD_CONST               3 (<code object Foo at
0x101102bb0, file "class_check.py", line 3>)
             15 MAKE_FUNCTION            0
             18 CALL_FUNCTION            0
             21 BUILD_CLASS
             22 STORE_FAST               1 (Foo)

  7          25 LOAD_FAST                0 (x)
             28 LOAD_FAST                1 (Foo)
             31 LOAD_ATTR                0 (x)
             34 BUILD_TUPLE              2
             37 PRINT_ITEM
             38 PRINT_NEWLINE
             39 LOAD_CONST               0 (None)
             42 RETURN_VALUE

========================================================

>>> dis(g)
 10           0 LOAD_CONST               1 (1)
              3 STORE_DEREF              0 (y)

 11           6 LOAD_CONST               2 ('Foo')
              9 LOAD_CONST               4 (())
             12 LOAD_CLOSURE             0 (y)
             15 BUILD_TUPLE              1
             18 LOAD_CONST               3 (<code object Foo at
0x101102d30, file "class_check.py", line 11>)
             21 MAKE_CLOSURE             0
             24 CALL_FUNCTION            0
             27 BUILD_CLASS
             28 STORE_FAST               0 (Foo)

 15          31 LOAD_FAST                0 (Foo)
             34 CALL_FUNCTION            0
             37 STORE_FAST               1 (foo)

 16          40 LOAD_DEREF               0 (y)
             43 LOAD_FAST                1 (foo)
             46 LOAD_ATTR                0 (y)
             49 LOAD_FAST                1 (foo)
             52 LOAD_ATTR                1 (gety)
             55 CALL_FUNCTION            0
             58 BUILD_TUPLE              3
             61 PRINT_ITEM
             62 PRINT_NEWLINE
             63 LOAD_CONST               0 (None)
             66 RETURN_VALUE

It can be noticed that x is stored as STORE_FAST while y is stored as
STORE_DEREF.

The excellent 'Python Innards series' (which I can understand only very
little) explains this  in one of the articles
http://tech.blog.aknin.name/2010/06/05/pythons-innards-naming/ :

"The secret sauce here is that at compilation time, if a variable is seen
to be resolved from a lexically nested function, it will not be stored and
will not be accessed using the regular naming opcodes. Instead, a special
object called a
cell<http://docs.python.org/py3k/c-api/cell.html#cell-objects>is
created to store the value of the object. When various code objects
(the
outer function, the inner function, etc) will access this variable, the use
of the *_DEREF opcodes will cause the cell to be accessed rather than the
namespace of the accessing code object."

additional reference: Python Closure:
Link1<http://ynniv.com/blog/2007/08/closures-in-python.html>
Link2 <http://www.shutupandship.com/2012/01/python-closures-explained.html>,


Regards,
Abdul Muneer

--
Follow me on Twitter: @abdulmuneer <http://twitter.com/#%21/abdulmuneer>


On Tue, Dec 4, 2012 at 4:26 PM, steve <steve at lonetwin.net> wrote:

> On Tuesday 04 December 2012 09:24 AM, Anand Chitipothu wrote:
>
>> Python scoping rules when it comes to classes are so confusing.
>>
>> Can you guess what would be output of the following program?
>>
>> x = 1
>>
>> class Foo:
>>      print(x)
>>
>
> Prints the global x
>
>
>       x = x + 1
>>      print(x)
>>
> Prints the local x, with the reference to the global x lost in the classes
> scope.
>
>
>> print(x, Foo.x)
>>
>
> prints (1, 2) -- ie: the 'global x' and the class local x. So, does the
> right thing. What were you expecting ?
>
>
>
>> Now take the same piece of code and put it in a function.
>>
>> def f():
>>      x = 1
>>
>>      class Foo:
>>          print(x)
>>          x = x + 1
>>          print(x)
>>
>>      print(x)
>>      print(Foo.x)
>>
>> f()
>>
>>
> Again, global versus local difference for the /class/. Still not sure what
> you were expecting,
>
>
>  To add more to your confusion, try this too:
>>
>> def g():
>>      y = 1
>>      class Foo:
>>          y = 2
>>          def gety(self):
>>              return y
>>
>>      foo = Foo()
>>      print(y, foo.y, foo.gety())
>>
>> g()
>>
>>  Ok, this is slightly confusing but still consistent. You'd understand
> the source of your confusion if you changed the definition for gety() to:
> ...
> ...
>         def gety(self):
>             return self.y
> ...
> ...
>
>
>
>  Does it make any sense?
>>
> Well, it does if you know the rules.
>
> http://effbot.org/pyfaq/what-**are-the-rules-for-local-and-**
> global-variables-in-python.htm<http://effbot.org/pyfaq/what-are-the-rules-for-local-and-global-variables-in-python.htm>
>
> Try this:
>
>
> x = 1
> class Foo:
>     print(x)  # this is the global x
>     x = x + 1 # this is the local x
>     print(x)
>     global x  # now lets be explicit
> print(x, Foo.x)
>
> what happened here ?
>
> cheers,
> - steve
>
>
> ______________________________**_________________
> BangPypers mailing list
> BangPypers at python.org
> http://mail.python.org/**mailman/listinfo/bangpypers<http://mail.python.org/mailman/listinfo/bangpypers>
>


More information about the BangPypers mailing list