Schrödinger's variable: the __class__ cell magically appears if you're checking for its presence?
This is a weird interaction in Python 3's implementation of no-argument super
. An access to super
in a method triggers the addition of a hidden __class__
closure variable referring to the class that defines the method. The parser special-cases a load of the name super
in a method by also adding __class__
to the method's symbol table, and then the rest of the relevant code all looks for __class__
instead of super
. However, if you try to access __class__
yourself, all the code looking for __class__
sees it and thinks it should do the super
handling!
Here's where it adds the name __class__
to the symbol table if it sees super
:
case Name_kind:
if (!symtable_add_def(st, e->v.Name.id,
e->v.Name.ctx == Load ? USE : DEF_LOCAL))
VISIT_QUIT(st, 0);
/* Special-case super: it counts as a use of __class__ */
if (e->v.Name.ctx == Load &&
st->st_cur->ste_type == FunctionBlock &&
!PyUnicode_CompareWithASCIIString(e->v.Name.id, "super")) {
if (!GET_IDENTIFIER(__class__) ||
!symtable_add_def(st, __class__, USE))
VISIT_QUIT(st, 0);
}
break;
Here's drop_class_free
, which sets ste_needs_class_closure
:
static int
drop_class_free(PySTEntryObject *ste, PyObject *free)
{
int res;
if (!GET_IDENTIFIER(__class__))
return 0;
res = PySet_Discard(free, __class__);
if (res < 0)
return 0;
if (res)
ste->ste_needs_class_closure = 1;
return 1;
}
The compiler section that checks ste_needs_class_closure
and creates the implicit cell:
if (u->u_ste->ste_needs_class_closure) {
/* Cook up an implicit __class__ cell. */
_Py_IDENTIFIER(__class__);
PyObject *tuple, *name, *zero;
int res;
assert(u->u_scope_type == COMPILER_SCOPE_CLASS);
assert(PyDict_Size(u->u_cellvars) == 0);
name = _PyUnicode_FromId(&PyId___class__);
if (!name) {
compiler_unit_free(u);
return 0;
}
...
There's more relevant code, but it's too much to include all of it. Python/compile.c
and Python/symtable.c
are where to look if you want to see more.
You can get some weird bugs if you try to use a variable named __class__
:
class Foo:
def f(self):
__class__ = 3
super()
Foo().f()
Output:
Traceback (most recent call last):
File "./prog.py", line 6, in <module>
File "./prog.py", line 4, in f
RuntimeError: super(): __class__ cell not found
The assignment to __class__
means __class__
is a local variable instead of a closure variable, so the closure cell super()
needs isn't there.
def f():
__class__ = 2
class Foo:
def f(self):
print(__class__)
Foo().f()
f()
Output:
<class '__main__.f.<locals>.Foo'>
Even though there's an actual __class__
variable in the enclosing scope, the special-casing of __class__
means you get the class instead of the enclosing scope's variable value.
Why does assigning to the __class__ cell break `super`?
You need a nonlocal
statement to assign to closure variables, including the magic __class__
closure variable. Assigning to __class__
without a nonlocal
statement creates a local variable that hides the magic closure variable.
You're expecting __class__
to behave as if it was local to meth
, but it actually behaves as if it's local to an invisible pseudo-scope in which all methods of Demo
are nested. If it was treated as local to meth
, you wouldn't need nonlocal
.
If you do add a nonlocal
statement, the implementation actually will allow you to reassign the magic closure variable:
class Foo:
def meth(self):
nonlocal __class__
__class__ = 3
super()
Foo().meth()
Result:
Traceback (most recent call last):
File "./prog.py", line 7, in <module>
File "./prog.py", line 5, in meth
RuntimeError: super(): __class__ is not a type (int)
Why __class__ can be directly accessed
So, it is a quirk for the compiler to find the parent class.
From Python Data Model:
__class__ is an implicit closure reference created by the compiler if any methods in a class body refer to either __class__ or super. This
allows the zero argument form of super() to correctly identify the
class being defined based on lexical scoping, while the class or
instance that was used to make the current call is identified based on
the first argument passed to the method.
How to get the class from which a method was called?
Walking the stack should give the answer.
The answer should ideally be, in the caller's stack frame.
The problem is, the stack frames only record the function
names (like so: 'f', 'g', 'h', etc.) Any information about
classes is lost. Trying to reverse-engineer the lost info,
by navigating the class hierarchy (in parallel with the
stack frame), did not get me very far, and got complicated.
So, here is a different approach:
Inject the class info into the stack frame
(e.g. with local variables),
and read that, from the called function.
import inspect
class A:
def f(self):
frame = inspect.currentframe()
callerFrame = frame.f_back
callerLocals = callerFrame.f_locals
return callerLocals['cls']
class B(A):
def g(self):
cls = B
return self.f()
def f(self):
cls = B
return super().f()
class C(B):
def h(self):
cls = C
return super(B, self).f()
def f(self):
cls = C
return super().f()
c = C()
assert c.h() == C
assert c.g() == B
assert c.f() == B
Related:
get-fully-qualified-method-name-from-inspect-stack
Without modifying the definition of subclasses:
Added an "external" decorator, to wrap class methods.
(At least as a temporary solution.)
import inspect
class Injector:
def __init__(self, nameStr, valueStr):
self.nameStr = nameStr
self.valueStr = valueStr
# Should inject directly in f's local scope / stack frame.
# As is, it just adds another stack frame on top of f.
def injectInLocals(self, f):
def decorate(*args, **kwargs):
exec(f'{self.nameStr} = {self.valueStr}')
return f(*args, **kwargs)
return decorate
class A:
def f(self):
frame = inspect.currentframe()
callerDecoratorFrame = frame.f_back.f_back # Note:twice
callerDecoratorLocals = callerDecoratorFrame.f_locals
return callerDecoratorLocals['cls']
class B(A):
def g(self): return self.f()
def f(self): return super().f()
class C(B):
def h(self): return super(B, self).f()
def f(self): return super().f()
bInjector = Injector('cls', B.__name__)
B.g = bInjector.injectInLocals(B.g)
B.f = bInjector.injectInLocals(B.f)
cInjector = Injector('cls', C.__name__)
C.h = cInjector.injectInLocals(C.h)
C.f = cInjector.injectInLocals(C.f)
c = C()
assert c.h() == C
assert c.g() == B
assert c.f() == B
I found this link very interesting
(but didn't take advantage of metaclasses here):
what-are-metaclasses-in-python
Maybe someone could even replace the function definitions*,
with functions whose code is a duplicate of the original;
but with added locals/information, directly in their scope.
*
Maybe after the class definitions have completed;
maybe during class creation (using a metaclass).
Why does super closure not use the new base class given by a metaclass?
The problem here is that your dynamic class does not inherit from itself (Class
) at all - the implicit __class__
variable inside your __init__
points to the "hardcoded" "Class" version - but the self
received when __init__
is called is an instance of the dynamic class, which does not have Class
as its superclass. Thus, the arguments filled implicitly to super()
: __class__
and self
will mismatch (self is not an instance of the class defined in __class__
or of a subclass of it).
The reliable way to fix this is to allow proper inheritance, and forget copying the class __dict__
attributes around: let the inheritance machinery take care of calling the methods in the appropriate order.
By simply making the dynamic class inherit from your static class and the dynamic-base, all methods are in place, and self
will be a proper instance from the baked-in __class__
from __init__
.__class__
still points to the static Class
, but the conditions for super to be called are fulfilled - and super does the right thing, by looking for the supermethods starting from the self
parameter - the newly created dynamic subclass which inherits both from your static Class
and the new Base
, and calls the methods on Base
as they are part of the __mro__
of the new class, in the correct place.
Ok - sounds complicated - but with some print statements added we can see the thing working:
class Base:
def __init__(self):
print("at base __init__")
class Meta(type):
def __call__(cls, obj, *args, **kwargs):
dynamic_ancestor = type(obj)
bases = (cls, dynamic_ancestor,)
new_cls = type(f"{cls.__name__}__{dynamic_ancestor.__name__}", bases , {})
instance = new_cls.__new__(new_cls, *args, **kwargs)
instance.__init__(*args, **kwargs)
return instance
class Class(metaclass=Meta):
def __init__(self, *args, **kwargs):
print(__class__.__bases__)
print(self.__class__.__bases__)
print(isinstance(self, Class))
print(isinstance(Class, Meta))
print(isinstance(self, __class__))
print(isinstance(self, self.__class__))
print(self.__class__.__mro__, __class__.__mro__)
super().__init__(*args, **kwargs)
Class(Base())
Outputs:
at base __init__
(<class 'object'>,)
(<class '__main__.Class'>, <class '__main__.Base'>)
True
True
True
True
(<class '__main__.Class__Base'>, <class '__main__.Class'>, <class '__main__.Base'>, <class 'object'>) (<class '__main__.Class'>, <class 'object'>)
at base __init__
Why can't you omit the arguments to super if you add *args in __init__ definition?
Per PEP 3135, which introduced "new super
" (emphasis mine):
The new syntax:
super()
is equivalent to:
super(__class__, <firstarg>)
where
__class__
is the class that the method was defined in, and
<firstarg>
is the first parameter of the method (normallyself
for
instance methods, andcls
for class methods).
There must be a specific first parameter for this to work (although it doesn't necessarily have to be called self
or cls
), it won't use e.g. args[0]
.
As to why it needs to be a specific parameter, that's due to the implementation; per the comment it uses the "first local variable on the stack". If co->co_argcount == 0
, as it is when you only specify *args
, you get the no arguments
error. This behaviour may not be the same in other implementations than CPython.
Related
- How is super() in Python 3 implemented?
- Why is Python 3.x's super() magic?
- Get "super(): no arguments" error in one case but not a similar case
- Schrödinger's variable: the __class__ cell magically appears if you're checking for its presence?
super() throwing an error in Sublime Text, works in PyCharm/Terminal
Your sublimetext is using the default build system, which is Python 2. Configure it to run in Python 3.
Tools -> Build System -> New Build System ...
Add this content:
{
"cmd": ["python3", "-u", "$file"],
"file_regex": "^[ ]*File \"(...*?)\", line ([0-9]*)",
"selector": "source.python"
}
Save the configuration with a sensible filename, say python3.sublime-build
, and select this newly created build in Tools -> Build With ...
.
Related Topics
Convert Pandas Series to Dataframe
Understanding Time.Perf_Counter() and Time.Process_Time()
Detect Text Region in Image Using Opencv
How to Break a Line of Chained Methods in Python
Typeerror: Unhashable Type: 'List' When Using Built-In Set Function
For Loops and Iterating Through Lists
Python Time + Timedelta Equivalent
Add Column with Number of Days Between Dates in Dataframe Pandas
How to Print the Key-Value Pairs of a Dictionary in Python
How to Specify "Nullable" Return Type with Type Hints
Selecting Pandas Column by Location
Pip Connection Failure: Cannot Fetch Index Base Url Http://Pypi.Python.Org/Simple/
No Module Named When Using Pyinstaller
Python Script Execute Commands in Terminal