Created
May 6, 2018 13:24
-
-
Save obriencj/40adc3f6db4d32ea04d5d61e9b25feb5 to your computer and use it in GitHub Desktop.
building classes in python
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def sample_1(parent=object): | |
class Sample(parent): | |
def __init__(self, val=None): | |
self.value = val | |
return Sample | |
def sample_2(parent=object): | |
class Sample(parent): | |
def __init__(self, val=None): | |
super().__init__(self) | |
self.value = val | |
return Sample | |
from dis import dis, show_code | |
from types import CodeType | |
def disassemble(obj, verbose=False): | |
if verbose: | |
show_code(obj) | |
print("Disassembly:") | |
dis(obj) | |
def find_code_const(obj, name): | |
obj = getattr(obj, "__code__", obj) | |
for c in obj.co_consts: | |
if isinstance(c, CodeType): | |
if c.co_name == name: | |
return c | |
else: | |
return None | |
# Sample1 = sample_1(object) | |
Sample1_class_code = find_code_const(sample_1, "Sample") | |
Sample1_init_code = find_code_const(Sample1_class_code, "__init__") | |
# Sample2 = sample_2(object) | |
Sample2_class_code = find_code_const(sample_2, "Sample") | |
Sample2_init_code = find_code_const(Sample2_class_code, "__init__") | |
if __name__ == "__main__": | |
print("\n === Sample1 class code ===") | |
disassemble(Sample1_class_code, True) | |
print("\n === Sample1 init code ===") | |
disassemble(Sample1_init_code, True) | |
print("\n === Sample2 class code ===") | |
disassemble(Sample2_class_code, True) | |
print("\n === Sample2 init code ===") | |
disassemble(Sample2_init_code, True) | |
# yuck. |
This is how the python 3.6 variation of a no-argument super()
was implemented. When detected at compile time, it causes the compiler to inject a closure for a value named __class__
into any method code which uses the super. That __class__
closure is then captured in the class' namespace as the __classcell__
. The type()
call which creates a type from the namespace resulting from calling the class code will check for the cell in a __classcell__
attribute, and if it finds one, will set the cell contents to the resulting class instance that type has created.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Here's a side-by-side comparison of the Sample1 class and init code disassemblies
https://gist.github.com/obriencj/a32ad3de87487e4ee731625633f35aa0/revisions?diff=split
Here's how that
__classcell__
closure is consumed by thetype()
callhttps://github.com/python/cpython/blob/b1c70d0ffbb235def1deab62a744ffd9b5253924/Objects/typeobject.c#L2690