a peek into python's metaclass and bytecode from a smalltalk user
TRANSCRIPT
A Peek into Python’s Metaclass and Bytecode as a Smalltalk User
Koan-Sin Tan freedom_at_computer.org
COSCUP 2015, Taipei
why this topic• ‘I was only vaguely aware of Smalltalk at the time; I remember being surprised
by its use of metaclasses (which is quite different from that in Python or Ruby!) when I read about them much later. Smalltalk's bytecode was a bigger influence of Python's bytecode though. I'd read about it in a book by Adele Goldberg and others, I believe "Smalltalk-80: The Language and its Implementation”’ [1]
• And, I had a talk comparing Ruby’s metaclass and byte code to Smalltalk’s in the end of 2012 [2]
• google “smalltalk and ruby bytecode”
[1] http://python-history.blogspot.com/2013/10/origin-of-metaclasses-in-python.html
[2] http://www.slideshare.net/kstan2/smalltalk-and-ruby-20121208-15542185
what won’t be discussed• anything beyond simple metaclass and bytecode
comparisons
• So, no inline cache if you was in the SpiderMonkey talk yesterday. Yes, I know inline cache was for Smalltalk-80. If you want to know how inline cache is used in Smalltalk, read an excellent article [1]
• And no other stuff in object model (whatever object model means to you)
[1] http://www.mirandabanda.org/cogblog/2011/03/01/build-me-a-jit-as-fast-as-you-can/
http://www.world.st/learn/books
who am I• Learnt to write program on MPF-II
• Used to be a programming language junkie
• Learnt a bit Smalltalk during early '90s, use it on and off
• Recent interest in ST-80 because of Scratch and BYOB/SNAP
• Knew little about Python
the first smalltalk-80 I used is on Sun’s SunView, image from Wikipedia
http://squeak.org
http://pharo.org
Two Popular Open Source Smalltalk Systems
Scratch was written in Smalltalk
8
http://news.softpedia.com/images/reviews/large/Scratch_Large_001.png
What I knew about python
• It’s a popular scripting language
• My classmate told me about Python in ’94 or ’95
• IPython notebook: using python and R to share notes
• And of course, Python is more than Smalltalk-80
share my understanding of python metaclass as bytecode as a common programmer
Quick Overview of ST-80• Object-Oriented
Programming
• OO GUI environment, IDE
• stack VM, bytecode
• Lambda, functional language, block
• Message passing
• Design Pattern:
• if you read the GoF book, you ran into lots of Smalltalk patterns before
• Learning/educational
• Logo and the dream of Dynabook
Metaclass
Metaclass in ST-80• From "purple book", Chap 16
1. Every class is ultimately a subclass of class Object, except for Object itself, which has no superclass. In particular, Class is a subclass of ClassDescription, which is a subclass of Behavior which is a subclass of Object
2. Every object is an instance of a class
3. Every class is an instance of a metaclass
4. All metaclasses are subclasses of Class
5. Every metaclass is an instance of Metaclass
6. The method of Class and it superclasses support the behavior common to all objects that are classes
7. The methods of instances of Metaclass add the behavior specific to particular classes
Smalltalk Object Model
•10 factorial --> 3628800 •10 factorial class --> SmallInteger •SmallInteger superclass --> Integer •Integersuperclass--> Number•Numbersuperclass--> Magnitude•Magnitudesuperclass--> Object•Objectsuperclass-->ProtoObject•ProtoObjectsuperclass-->nil
14
• 10 factorial class allSuperclasses --> an OrderedCollection(Integer Number Magnitude Object ProtoObject)
Python intprint(type(1))<class 'int'>
print(type(1).__base__)<class 'object'>
print(type(1).__base__.__base__)None
print(type(1).__mro__)(<class 'int'>, <class ‘object'>)
• So int, then object, no hierarchy for numbers. How about container types, e.g., set and dict? NO!
• Fortunately, there is another path since python3000(?)
• There is PEP 3119 “Abstract Base Classes” (ABC) and its companion PEP 3141. So, there are ABCs for numbers in module numbers and collections module collections
numbers.Integral.__base__<class 'numbers.Rational'>
numbers.Integral.__base__.__base__<class 'numbers.Real'>
numbers.Integral.__base__.__base__.__base__<class 'numbers.Complex'>
numbers.Integral.__base__.__base__.__base__.__base__<class 'numbers.Number'>
numbers.Integral.__base__.__base__.__base__.__base__.__base__<class ‘object'>
numbers.Integral.__mro__(<class 'numbers.Integral'>, <class 'numbers.Rational'>, <class 'numbers.Real'>, <class 'numbers.Complex'>, <class 'numbers.Number'>, <class 'object'>)
https://docs.python.org/3/reference/datamodel.html
Number
Complex
Real
Integral
10
Rational
object
Keyinstance-of
a class named Metaclass
SmallIntegerclass-->SmallIntegerclassIntegerclass-->IntegerclassNumberclass-->NumberclassMagnitudeclass-->MagnitudeclassObjectclass-->ObjectclassProtoObjectclass-->ProtoObjectclass
SmallInteger class superclass --> Integer class Integer class superclass --> Number class Number class superclass --> Magnitude class Magnitude class superclass --> Object class Object class superclass --> ProtoObject class ProtoObject class superclass --> Class
Class class --> Class class Class class class --> Metaclass
Metaclass class --> Metaclass class Metaclass class class --> Metaclass
Object
Magnitude
Number
Object class
Magnitude class
Number class
Keyinstance-of
Integer class
SmallInteger SmallInteger class
10
Integer
ProtoObject ProtoObject class
figures modified from “Pharo by Example”
18
Object
Magnitude
Number
Object class
Magnitude class
Number class
Keyinstance-of
Integer class
SmallInteger SmallInteger class
10
Integer
ProtoObject ProtoObject class
Class
Class class
Metaclass
Metaclass class
19
Class class class --> Metaclass Metaclass superclass --> ClassDescription ClassDescription superclass --> Behavior Behavior superclass --> ObjectClass class superclass --> ClassDescription class ClassDescription class superclass --> Behavior class Behavior class superclass --> Object class
20
Object
Magnitude
Number
Object class
Magnitude class
Number class
Keyinstance-of
Integer classSmallInteger
SmallInteger class10
Integer
ProtoObject
ProtoObject class
Class
Class class
Metaclass
Metaclass class
ClassDescription
BehaviorClassDescription class
Behavior class
21
from “PBE”
Python metaclasstype(1).__class__<class ‘type'>
type(1).__class__.__class_<class ‘type’>
numbers.Integral.__class__<class ‘abc.ABC’>
numbers.Rationl.__class__<class ‘abc.ABC’>
abc.ABC.__class__<class ‘type’>————————————————————————————————
import disdef testClass(): class Foo: def foo(self, a, b): return a + b bar = Foo() bar.foo(2+3) dis.dis(testClass)
3 0 LOAD_BUILD_CLASS 1 LOAD_CONST 1 (<code object Foo at 0x1101aee40, file "<ipython-input-37-12f9deff1239>", line 3>) 4 LOAD_CONST 2 ('Foo') 7 MAKE_FUNCTION 0 10 LOAD_CONST 2 ('Foo') 13 CALL_FUNCTION 2 (2 positional, 0 keyword pair) 16 STORE_FAST 0 (Foo)
6 19 LOAD_FAST 0 (Foo) 22 CALL_FUNCTION 0 (0 positional, 0 keyword pair) 25 STORE_FAST 1 (bar)
7 28 LOAD_FAST 1 (bar) 31 LOAD_ATTR 0 (foo) 34 LOAD_CONST 5 (5) 37 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 40 POP_TOP 41 LOAD_CONST 0 (None) 44 RETURN_VALUE
Python metaclass• LOAD_BUILD_CLASS:LOAD_BUILD_CLASS¶Pushes builtins.__build_class__() onto the stack. It is later called by CALL_FUNCTION to construct a class [1]
• builtins.__build_class__ static PyMethodDef builtin_methods[] = { {"__build_class__", (PyCFunction)builtin___build_class__, METH_VARARGS | METH_KEYWORDS, build_class_doc}, {"__import__", (PyCFunction)builtin___import__, METH_VARARGS | METH_KEYWORDS, import_doc}, {"abs", builtin_abs, METH_O, abs_doc},
[1] https://docs.python.org/3/library/dis.html
• builtin_build_class__
• if no specific metaclass was given and there no special metaclass in base classes of a class, the default metaclass is the ‘type’
• BTW, as far as I can tell there are some classes have non-type metaclasses in standard Python distribution. We know numbers and collections
• try “grep metaclass=“ in python source code
builtin_build_class__()static PyObject * builtin___build_class__(PyObject *self, PyObject *args, PyObject *kwds) { …
if (meta == NULL) { /* if there are no bases, use type: */ if (PyTuple_GET_SIZE(bases) == 0) { meta = (PyObject *) (&PyType_Type); } /* else get the type of the first base */ else { PyObject *base0 = PyTuple_GET_ITEM(bases, 0); meta = (PyObject *) (base0->ob_type); } Py_INCREF(meta); isclass = 1; /* meta is really a class */ } … }
Some others
another use of python’s ABCs
isinstance(10, int)True
isinstance(10, complex)false
isinstance(10, numbers.Rational)True
isinstance(10, numbers.Real)True
isinstance(10, numbers.Complex)True
isinstance(10, numbers.Number)True
Number
Complex
Real
Integral
10
Rational
object
Keyinstance-of
bytecode
Bytecode• Bytecode is not new at all
• Smalltalk is one of early bytecode users
• Smalltalk bytecode
• end of Chapter 26, http://www.mirandabanda.org/bluebook/bluebook_chapter26.html#TheBytecodes26
• Chap. 28, http://www.mirandabanda.org/bluebook/bluebook_chapter28.html
Smalltalk bytecode categories• pushes• indicatesthesourceofanobjecttobeaddedtothetopoftheinterpreter'sstack
• stores• indicatesthevariablewhosevalueshouldbechanged
• sends• specifiestheselectorofamessagetobesentandhowmanyargumentsitshouldhave.
• returns• avalueisreturnedforthemessagethatinvokedthatCompiledMethod
• andjumps• youknowwhattheseare
30
SmalltalkbytecodesRange Bits FuncHon0-15 0000iiii PushReceiverVariable#iiii16-31 0001iiii PushTemporaryLocaHon#iiii32-63 001iiiii PushLiteralConstant#iiiii64-95 010iiiii PushLiteralVariable#iiiii96-103 01100iii PopandStoreReceiverVariable#iii104-111 01101iii PopandStoreTemporaryLocaHon#iii112-119 01110iii Push(receiver,true,false,nil,-1,0,1,2)[iii]120-123 011110ii Return(receiver,true,false,nil)[ii]FromMessage124-125 0111110i ReturnStackTopFrom(Message,Block)[i]126-127 0111111i unused128 10000000jjkkkkkk Push(ReceiverVariable,TemporaryLocaHon,LiteralConstant,LiteralVariable)[jj]#kkkkkk129 10000001jjkkkkkk Store(ReceiverVariable,TemporaryLocaHon,Illegal,LiteralVariable)[jj]#kkkkkk130 10000010jjkkkkkk PopandStore(ReceiverVariable,TemporaryLocaHon,Illegal,LiteralVariable)[jj]#kkkkkk131 10000011jjjkkkkk SendLiteralSelector#kkkkkWithjjjArguments132 10000100jjjjjjjjkkkkkkkk SendLiteralSelector#kkkkkkkkWithjjjjjjjjArguments133 10000101jjjkkkkk SendLiteralSelector#kkkkkToSuperclassWithjjjArguments134 10000110jjjjjjjjkkkkkkkk SendLiteralSelector#kkkkkkkkToSuperclassWithjjjjjjjjArguments135 10000111 PopStackTop136 10001000 DuplicateStackTop137 10001001 PushAcHveContext138-143 unused144-151 10010iii Jumpiii+1(i.e.,1through8)152-159 10011iii PopandJump0nFalseiii+1(i.e.,1through8)160-167 10100iiijjjjjjjj Jump(iii-4)*256+jjjjjjjj168-171 101010iijjjjjjjj PopandJumpOnTrueii*256+jjjjjjjj172-175 101011iijjjjjjjj PopandJumpOnFalseii*256+jjjjjjjj176-191 1011iiii SendArithmeHcMessage#iiii192-207 1100iiii SendSpecialMessage#iiii208-223 1101iiii SendLiteralSelector#iiiiWithNoArguments224-239 1110iiii SendLiteralSelector#iiiiWith1Argument240-255 1111iiii SendLiteralSelector#iiiiWith2Arguments
31
• Anexamplemethod,forCompiledMethod "to show how CompiledMethod works" | foo | foo := 'test'. ^ 1 + 2
• Bytecode:inSystemBrowserofSqueak/Pharo,wecanseebytecodeeasily17 <20> pushConstant: 'test' 18 <68> popIntoTemp: 0 19 <76> pushConstant: 1 20 <77> pushConstant: 2 21 <B0> send: + 22 <7C> returnTop
32
foo | foo | foo := 'test'. ^ 1 + 2
17 <20> pushConstant: ‘test'18 <68> popIntoTemp: 019 <76> pushConstant: 120 <77> pushConstant: 221 <B0> send: +22 <7C> returnTop
def test(): foo = "test" return 1 + 2
dis.dis(test)
2 0 LOAD_CONST 1 ('test') 3 STORE_FAST 0 (foo)
3 6 LOAD_CONST 4 (3) 9 RETURN_VALUE
foo: a and: b "to show how CompiledMethod works" | foo | foo := 'test'. ^ a + b
17 <20> pushConstant: 'test'18 <6A> popIntoTemp: 219 <10> pushTemp: 020 <11> pushTemp: 121 <B0> send: +22 <7C> returnTop
def test(a, b): foo = "test" return a + b
dis.dis(test)
2 0 LOAD_CONST 1 ('test') 3 STORE_FAST 2 (foo)
3 6 LOAD_FAST 0 (a) 9 LOAD_FAST 1 (b) 12 BINARY_ADD 13 RETURN_VALUE
displaying smalltalk bytecode
• CompiledMethod
• Class>>compiledMethodAt
• (Integer compiledMethodAt: #factorial) symbolic.
• (COSCUP2015 compiledMethodAt: #forCompiledMethod) symbolic.
(Integer compiledMethodAt: #factorial) symbolic.'29 <70> self30 <75> pushConstant: 031 <B6> send: =32 <99> jumpFalse: 35 33 <76> pushConstant: 134 <7C> returnTop 35 <70> self36 <75> pushConstant: 037 <B3> send: >38 <9E> jumpFalse: 46 39 <70> self40 <70> self41 <76> pushConstant: 142 <B1> send: -43 <D0> send: factorial44 <B8> send: *45 <7C> returnTop 46 <70> self47 <22> pushConstant: ''Not valid for negative integers'' 48 <E1> send: error:49 <87> pop50 <78> returnSelf '
(COSCUP2015 compiledMethodAt: #forCompiledMethod) symbolic.'17 <20> pushConstant: ''test'' 18 <68> popIntoTemp: 0 19 <76> pushConstant: 120 <77> pushConstant: 221 <B0> send: +22 <7C> returnTop '
Add a methodCOSCUP2015 compile: ' foo ^42'
Add a class|myClassmyInstance| myClass:=Behaviornew."createanonbehavior"myClasscompile:'theAnswer^42'."addamethodforinstances"(myClasscompiledMethodAt:#theAnswer)symbolic. myInstance:=myClassnew."createaninstance"Transcriptshow:myInstancetheAnswer;cr."shows42”
Python Bytecode• General
• Unary
• Binary
• In-place
• Misc
• print: PRINT_EXPR
• set, map, tuple, list, etc.
https://docs.python.org/3/library/dis.html
foobar: a and: b "to show how CompiledMethod works" | foo | foo := 'test'. ^ Array with: (a + b) with: foo
25 <20> pushConstant: ‘test'26 <6A> popIntoTemp: 227 <41> pushLit: Array28 <10> pushTemp: 029 <11> pushTemp: 130 <B0> send: +31 <12> pushTemp: 232 <F2> send: with:with:33 <7C> returnTop
import disdef foo(a, b): foo = "test" return (a+b, foo)
dis.dis(foo)
3 0 LOAD_CONST 1 ('test') 3 STORE_FAST 2 (foo)
4 6 LOAD_FAST 0 (a) 9 LOAD_FAST 1 (b) 12 BINARY_ADD 13 LOAD_FAST 2 (foo) 16 BUILD_TUPLE 2 19 RETURN_VALUE
observations• ST-80’s bytecode is relatively simple or say
primitive one
• Python’s bytecode has many some unique instructions and some specialized instructions for builtin types (for legacy or performance reasons, maybe)
• I know ST-80’s bytecode showed its age, but I am surprised that Python’s bytecode is low level and old too
So?
• We walked through metaclass and bytecode in ST-80 and Python? Do you agree Guido’s words I cited previously
https://www.moedict.tw/' ?font=ebas