user881300
user881300

Reputation: 212

Python instance of class defined inside a method is still able to use local variables inside the method when called outside the method

I have a class defined inside a python method as follows

c = None
def foo():
   i = 1
   class bar( object ):
      def show( self ):
         print i
   global c
   c = bar()
foo()
print c.show() #prints 1

The above code prints 1 which is thee value of i .

Where is the 'i' stored for the instance of bar to access ? Instances created outside the class using c.__class__() can also access i

Upvotes: 0

Views: 97

Answers (3)

mgilson
mgilson

Reputation: 309929

You're accessing the variable via a closure. In python2.x, i's value is actually stored on the show function object in the func_closure1 attribute:

>>> c = None
>>> def foo():
...    i = 1
...    class bar( object ):
...       def show( self ):
...          print i
...    global c
...    c = bar()
... 
>>> foo()
>>> c.show.im_func.func_closure[0].cell_contents
1

We can gain a little more insight by looking at the disassembed bytecode:

>>> dis.dis(c.show.im_func)
  5           0 LOAD_DEREF               0 (i)
              3 PRINT_ITEM          
              4 PRINT_NEWLINE       
              5 LOAD_CONST               0 (None)
              8 RETURN_VALUE 

Ahh, so it looks like objects that are accessed via closures are loaded by the LOAD_DEREF OP code which also tells python which cell to look in (in this case the 0th).

1__closure__ and __func__ are the python3.x names for func_closure and im_func, respectively. They're aliased in python2.6 to aid with writing forward compatible code.

Upvotes: 3

Martijn Pieters
Martijn Pieters

Reputation: 1122282

You are accessing a closure; i is closed over by the nested scope of bar.show(). The same would happen to variables used by nested functions without a class.

You can see the closure by introspecting the method; I've altered your code a little to just return the class as it simplifies things:

>>> def foo():
...    i = 1
...    class bar( object ):
...       def show( self ):
...          print i
...    return bar
... 
>>> c = foo()()
>>> c
<__main__.bar object at 0x104c7ac90>
>>> c.show
<bound method bar.show of <__main__.bar object at 0x104c7ac90>>
>>> c.show.__func__
<function show at 0x10535c758>
>>> c.show.__func__.__closure__
(<cell at 0x1132a0440: int object at 0x100502818>,)
>>> c.show.__func__.__closure__[0].cell_contents
1

So c.show is a bound method, c.show.__func__ the original show function, and c.show.__func__.__closure__ is the tuple of closure cells. I've accessed the current value of the cell with the closure.cell_contents attribute.

The parent function foo, is also aware that the variable i must be kept around, Python recorded that information in the code object for the function:

>>> foo.__code__.co_cellvars
('i',)

and the bytecode for the function creates a closure cell as part of the code:

>>> import dis
>>> dis.dis(foo)
  2           0 LOAD_CONST               1 (1)
              3 STORE_DEREF              0 (i)

  3           6 LOAD_CONST               2 ('bar')
              9 LOAD_GLOBAL              0 (object)
             12 BUILD_TUPLE              1
             15 LOAD_CLOSURE             0 (i)
             18 BUILD_TUPLE              1
             21 LOAD_CONST               3 (<code object bar at 0x1132a4bb0, file "<stdin>", line 3>)
             24 MAKE_CLOSURE             0
             27 CALL_FUNCTION            0
             30 BUILD_CLASS         
             31 STORE_FAST               0 (bar)

  6          34 LOAD_FAST                0 (bar)
             37 RETURN_VALUE        

The LOAD_CLOSURE and MAKE_CLOSURE byte codes work together to produce closure cells and passing it on to the code object that will produce the class (handled by the LOAD_CONST bytecode at index 21); the class bytecode passes that closure on again to the show function:

>>> dis.dis(foo.__code__.co_consts[3])
  3           0 LOAD_NAME                0 (__name__)
              3 STORE_NAME               1 (__module__)

  4           6 LOAD_CLOSURE             0 (i)
              9 BUILD_TUPLE              1
             12 LOAD_CONST               0 (<code object show at 0x1132a4330, file "<stdin>", line 4>)
             15 MAKE_CLOSURE             0
             18 STORE_NAME               2 (show)
             21 LOAD_LOCALS         
             22 RETURN_VALUE        

Inside bar.show(), the closure is also handled with a special bytecode:

>>> dis.dis(c.show)
  5           0 LOAD_DEREF               0 (i)
              3 PRINT_ITEM          
              4 PRINT_NEWLINE       
              5 LOAD_CONST               0 (None)
              8 RETURN_VALUE        

The LOAD_DEREF opcode loads the cell at index 0 just as I did above.

Upvotes: 2

Ivan
Ivan

Reputation: 6013

This is normal behavior. The class that you defined will work like a closure function: it will have access to its enclosing scope -- in your case the foo function.

You can refer to the official documentation on scopes and namespaces.

Upvotes: 0

Related Questions