5

python relies on the __class__ variable to be in a cell for a super() call. It gets this cell from the free variables in the first stack frame.

The odd thing is though that this variable isn't in locals(), and it is when you just reference it from the __init__ method.

Take for example this bit of code:

class LogicGate:
    def __init__(self,n):
        print(locals())
        a = __class__
        print(locals())

When you disassemble this you can see it somehow knows that print and locals are globals and __class__ is a LOAD_DEREF. How does the compiler know this, before running the code. locals, print and __class__ are just variable names to the compiler as far as I know. Also this way __class__ is all of a sudden in the locals() even before it's copied into a.

4          10 LOAD_DEREF               0 (__class__)

while locals:

            2 LOAD_GLOBAL              1 (locals)

I'm asking because I'm working on skulpt a python to javascript compiler. And currently that compiler doesn't differentiate between print or __class__ and attempts to get them both from the global scope.

As you can see from a printout of the ast of the above bit of code, the parser doesn't differentiate between locals or __class__:

Module(body=[ClassDef(name='LogicGate',
    bases=[],
    keywords=[],
    body=[FunctionDef(name='__init__',
        args=arguments(args=[arg(arg='self',
                                 annotation=None),
                             arg(arg='n',
                                  annotation=None)],
                       vararg=None,
                       kwonlyargs=[],
                       kw_defaults=[],
                       kwarg=None,
                       defaults=[]),
        body=[Expr(value=Call(func=Name(id='print',
                                        ctx=Load()),
                                              # here's the load for locals
                              args=[Call(func=Name(id='locals',
                                                   ctx=Load()),
                                         args=[],
                                         keywords=[])],
                              keywords=[])),
              Assign(targets=[Name(id='a',
                                   ctx=Store())],
                           # here's the load for __class__
                     value=Name(id='__class__',
                                ctx=Load())),
              Expr(value=Call(func=Name(id='print',
                                        ctx=Load()),
                              args=[Call(func=Name(id='locals',
                                                   ctx=Load()),
                                         args=[],
                                         keywords=[])],
                              keywords=[]))],
        decorator_list=[],
        returns=None)],
   decorator_list=[])])
albertjan
  • 7,739
  • 6
  • 44
  • 74
  • I found part if the issue, in python2 you can't reference `__class__` from nowhere. And it doesn't to a DEREF. – albertjan Aug 05 '17 at 13:57
  • Are you using python 2 or python 3? The `__class__` cell is a Python 3 hack to allow `super` to be called without any args. – Dunes Aug 05 '17 at 14:05

1 Answers1

6

The __class__ cell is a hack in Python 3 to allow super to be called without args. In Python 2 you had to call super with boilerplate arguments (ie.super(<current class>, self)).

The __class__ cell itself is stored in the <function>.__closure__ tuple. The index of the __class__ cell can be obtained by finding its index in the <function>.__code__.co_freevars tuple. For instance,

>>> class A:
    def __init__(self):
        super().__init__()

>>> A.__init__.__code__.co_freevars
('__class__',)
>>> A.__init__.__closure__
(<cell at 0x03EEFDF0: type object at 0x041613E0>,)
>>> A.__init__.__closure__[
        A.__init__.__code__.co_freevars.index('__class__')
    ].cell_contents
<class '__main__.A'>

However, depending on the function, co_freevars and __closure__ may be None if the function doesn't use cells. Further, __class__ is not guaranteed to be present. The __class__ cell is only present if a function called super is called without args (doesn't actually have to be super eg. super = print; super() will fool the compiler into creating a __class__ cell) or if __class__ is explicitly referenced and is not local. You also cannot assume that the __class__ cell is always at index 0, as the following (albeit bizarre) code shows:

class A:
    def greet(self, person):
        print('hello', person)

def create_B(___person__):
    class B(A):
        def greet(self):
            super().greet(___person__)
    return B

B = create_B('bob')
B().greet() # prints hello bob

assert [c.cell_contents for c in B.greet.__closure__] == ['bob', B]
Dunes
  • 37,291
  • 7
  • 81
  • 97
  • 1
    Great thanks! This definitely clears up how this all stuck together. And why all of a sudden that `__class__` var shows up. This is the PR I'm working on to add `super` to skulpt. https://github.com/skulpt/skulpt/pull/694 it's a python2 implementation with some added py3 stuff. And finding the type of the class is turning out to be interesting. – albertjan Aug 06 '17 at 09:24
  • Like I said, the implementation of `super` in py 3 is very hacky. It revolves around the compiler detecting that a function defined in a class calls a function called `super`, it then creates that function with a closure around the class currently being defined. – Dunes Aug 06 '17 at 18:56
  • `__class__` (and subsequently `super()` calls) seem to break when wrapping classes with decorators that return new classes. – Cobertos Mar 07 '18 at 00:40
  • 1
    @Coburn I've never had a problem with with that. Maybe you should ask a new question with an example that shows the problem. If you do, please tag me in a comment on the question. – Dunes Mar 07 '18 at 11:17