Python class method closure
Python's class methods are functions nested in the class's block. Internally, a class block is very similar to a function block, with the exception that nested functions in it, i.e. the class methods, do not support accessing class attributes as closure variables. For example,
def f():
v = 100
def g():
return v
g()
works but
class f(object):
v = 100
def g():
return v
g()
fails with NameError
for v
because it is assumed to be a global variable.
Why the class attribute name v
is out there but the class method g
can not see it?
This is a design decision of Python and it is implemented at here, where names visible in current block are made visible to child blocks only if the current block is not a class block. So if it is a class block, class methods, i.e. child blocks, really can not see the class attribute name v
, as a result v
in the class method g
is assumed to be a global variable instead.
We can hack Python's source code to implement class method closure. Several places need to be patched.
At here, change
if (ste->ste_type != ClassBlock) {
to
if (TRUE) {
At here, change
if (ste->ste_type == FunctionBlock) {
to
if (ste->ste_type == FunctionBlock || ste->ste_type == ClassBlock) {
At here, change
else {
to
if (ste->ste_type == ClassBlock) {
At here, change
if (ste->ste_type == FunctionBlock && !analyze_cells(scopes, newfree))
to
if ((ste->ste_type == FunctionBlock || ste->ste_type == ClassBlock) && !analyze_cells(scopes, newfree))
At here, change
else if (ste->ste_type == ClassBlock && !drop_class_free(ste, newfree))
to
if (ste->ste_type == ClassBlock && !drop_class_free(ste, newfree))
At here, change
assert(PyDict_GET_SIZE(c->u->u_cellvars) == 0);
to
//assert(PyDict_GET_SIZE(c->u->u_cellvars) == 0);
Now the code below works:
class Test(object):
CLASS_FIELD = 100
def get_class_field(self):
return CLASS_FIELD
def set_class_field(self, value):
nonlocal CLASS_FIELD
CLASS_FIELD = value
test = Test()
assert test.get_class_field() == 100
test.set_class_field(200)
assert test.get_class_field() == 200
Notice a subtlety here that with class attributes accessed as closure variables in class methods, change of a class attribute's value via attribute assignment will not be perceived by the class methods because they are bound to access the closure variable, instead of doing an attribute lookup on the class object to get the new value. For example:
class Test(object):
CLASS_FIELD = 100
def get_class_field(self):
return CLASS_FIELD
def set_class_field(self, value):
nonlocal CLASS_FIELD
CLASS_FIELD = value
test = Test()
Test.CLASS_FIELD = 200
assert test.get_class_field() == 100
test.set_class_field(300)
assert test.get_class_field() == 300
assert Test.CLASS_FIELD == 200
Comments: