Skip to content

Instantly share code, notes, and snippets.

@jcrist
Last active December 11, 2015 18:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jcrist/2b97c9bcc0b95caa73ce to your computer and use it in GitHub Desktop.
Save jcrist/2b97c9bcc0b95caa73ce to your computer and use it in GitHub Desktop.
Manipulating Python Bytecode with Codetransformer
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Python Bytecode"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"def foo(a, b, c):\n",
" if a:\n",
" return b + c\n",
" return b - c"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"5"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"foo(1, 2, 3)"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"<code object foo at 0x104e10db0, file \"<ipython-input-1-987819d4effb>\", line 1>"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"foo.__code__"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"b'|\\x00\\x00r\\x0e\\x00|\\x01\\x00|\\x02\\x00\\x17S|\\x01\\x00|\\x02\\x00\\x18S'"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"foo.__code__.co_code"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" 2 0 LOAD_FAST 0 (a)\n",
" 3 POP_JUMP_IF_FALSE 14\n",
"\n",
" 3 6 LOAD_FAST 1 (b)\n",
" 9 LOAD_FAST 2 (c)\n",
" 12 BINARY_ADD\n",
" 13 RETURN_VALUE\n",
"\n",
" 4 >> 14 LOAD_FAST 1 (b)\n",
" 17 LOAD_FAST 2 (c)\n",
" 20 BINARY_SUBTRACT\n",
" 21 RETURN_VALUE\n"
]
}
],
"source": [
"from dis import dis\n",
"dis(foo)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"BINARY_ADD\n",
"BINARY_AND\n",
"BINARY_FLOOR_DIVIDE\n",
"BINARY_LSHIFT\n",
"BINARY_MODULO\n",
"BINARY_MULTIPLY\n",
"BINARY_OR\n",
"BINARY_POWER\n",
"BINARY_RSHIFT\n",
"BINARY_SUBSCR\n",
"BINARY_SUBTRACT\n",
"BINARY_TRUE_DIVIDE\n",
"BINARY_XOR\n",
"BREAK_LOOP\n",
"BUILD_LIST\n",
"BUILD_MAP\n",
"BUILD_SET\n",
"BUILD_SLICE\n",
"BUILD_TUPLE\n",
"CALL_FUNCTION\n",
"CALL_FUNCTION_KW\n",
"CALL_FUNCTION_VAR\n",
"CALL_FUNCTION_VAR_KW\n",
"COMPARE_OP\n",
"CONTINUE_LOOP\n",
"DELETE_ATTR\n",
"DELETE_DEREF\n",
"DELETE_FAST\n",
"DELETE_GLOBAL\n",
"DELETE_NAME\n",
"DELETE_SUBSCR\n",
"DUP_TOP\n",
"DUP_TOP_TWO\n",
"END_FINALLY\n",
"EXTENDED_ARG\n",
"FOR_ITER\n",
"GET_ITER\n",
"IMPORT_FROM\n",
"IMPORT_NAME\n",
"IMPORT_STAR\n",
"INPLACE_ADD\n",
"INPLACE_AND\n",
"INPLACE_FLOOR_DIVIDE\n",
"INPLACE_LSHIFT\n",
"INPLACE_MODULO\n",
"INPLACE_MULTIPLY\n",
"INPLACE_OR\n",
"INPLACE_POWER\n",
"INPLACE_RSHIFT\n",
"INPLACE_SUBTRACT\n",
"INPLACE_TRUE_DIVIDE\n",
"INPLACE_XOR\n",
"JUMP_ABSOLUTE\n",
"JUMP_FORWARD\n",
"JUMP_IF_FALSE_OR_POP\n",
"JUMP_IF_TRUE_OR_POP\n",
"LIST_APPEND\n",
"LOAD_ATTR\n",
"LOAD_BUILD_CLASS\n",
"LOAD_CLASSDEREF\n",
"LOAD_CLOSURE\n",
"LOAD_CONST\n",
"LOAD_DEREF\n",
"LOAD_FAST\n",
"LOAD_GLOBAL\n",
"LOAD_NAME\n",
"MAKE_CLOSURE\n",
"MAKE_FUNCTION\n",
"MAP_ADD\n",
"NOP\n",
"POP_BLOCK\n",
"POP_EXCEPT\n",
"POP_JUMP_IF_FALSE\n",
"POP_JUMP_IF_TRUE\n",
"POP_TOP\n",
"PRINT_EXPR\n",
"RAISE_VARARGS\n",
"RETURN_VALUE\n",
"ROT_THREE\n",
"ROT_TWO\n",
"SETUP_EXCEPT\n",
"SETUP_FINALLY\n",
"SETUP_LOOP\n",
"SETUP_WITH\n",
"SET_ADD\n",
"STORE_ATTR\n",
"STORE_DEREF\n",
"STORE_FAST\n",
"STORE_GLOBAL\n",
"STORE_MAP\n",
"STORE_NAME\n",
"STORE_SUBSCR\n",
"UNARY_INVERT\n",
"UNARY_NEGATIVE\n",
"UNARY_NOT\n",
"UNARY_POSITIVE\n",
"UNPACK_EX\n",
"UNPACK_SEQUENCE\n",
"WITH_CLEANUP\n",
"YIELD_FROM\n",
"YIELD_VALUE\n"
]
}
],
"source": [
"from opcode import opmap\n",
"list(map(print, sorted(opmap)));"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Run in python interpreter loop - basically giant switch statement on opcodes: https://github.com/python/cpython/blob/aed79b41a1fbcedd4697269e3fdd40af5ee82b14/Python/ceval.c#L1357"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"# Transforming Bytecode with `codetransformer`\n",
"\n",
"https://github.com/llllllllll/codetransformer"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"code(argcount, kwonlyargcount, nlocals, stacksize, flags, codestring,\n",
" constants, names, varnames, filename, name, firstlineno,\n",
" lnotab[, freevars[, cellvars]])\n",
"\n",
"Create a code object. Not for the faint of heart.\n"
]
}
],
"source": [
"from types import CodeType\n",
"print(CodeType.__doc__)"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"from codetransformer import CodeTransformer\n",
"from codetransformer import instructions as ir\n",
"from codetransformer.patterns import pattern"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## Tracing"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"def log(var):\n",
" return lambda val: print('{0} = {1}'.format(var, repr(val))) or val\n",
"\n",
"\n",
"class trace(CodeTransformer):\n",
" def __init__(self, vars=None):\n",
" super().__init__()\n",
" self._vars = vars\n",
"\n",
" @pattern(ir.STORE_FAST | ir.STORE_GLOBAL | ir.STORE_NAME)\n",
" def _store(self, instr):\n",
" if not self._vars or instr.arg in self._vars:\n",
" yield ir.LOAD_CONST(log(instr.arg)).steal(instr)\n",
" yield ir.ROT_TWO()\n",
" yield ir.CALL_FUNCTION(positional=1)\n",
" yield instr"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"@trace()\n",
"def sum_word_lengths(words):\n",
" total = 0\n",
" for w in words:\n",
" word_length = len(w)\n",
" total += word_length\n",
" return total"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"total = 0\n",
"w = 'apple'\n",
"word_length = 5\n",
"total = 5\n",
"w = 'banana'\n",
"word_length = 6\n",
"total = 11\n",
"w = 'pear'\n",
"word_length = 4\n",
"total = 15\n",
"w = 'orange'\n",
"word_length = 6\n",
"total = 21\n"
]
},
{
"data": {
"text/plain": [
"21"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"sum_word_lengths(['apple', 'banana', 'pear', 'orange'])"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" 3 0 LOAD_CONST 0 (0)\n",
" 3 LOAD_CONST 1 (<function log.<locals>.<lambda> at 0x106b18840>)\n",
" 6 ROT_TWO\n",
" 7 CALL_FUNCTION 1 (1 positional, 0 keyword pair)\n",
" 10 STORE_FAST 1 (total)\n",
"\n",
" 4 13 SETUP_LOOP 57 (to 73)\n",
" 16 LOAD_FAST 0 (words)\n",
" 19 GET_ITER\n",
" >> 20 FOR_ITER 49 (to 72)\n",
" 23 LOAD_CONST 2 (<function log.<locals>.<lambda> at 0x106bfdf28>)\n",
" 26 ROT_TWO\n",
" 27 CALL_FUNCTION 1 (1 positional, 0 keyword pair)\n",
" 30 STORE_FAST 2 (w)\n",
"\n",
" 5 33 LOAD_GLOBAL 0 (len)\n",
" 36 LOAD_FAST 2 (w)\n",
" 39 CALL_FUNCTION 1 (1 positional, 0 keyword pair)\n",
" 42 LOAD_CONST 3 (<function log.<locals>.<lambda> at 0x106bfdea0>)\n",
" 45 ROT_TWO\n",
" 46 CALL_FUNCTION 1 (1 positional, 0 keyword pair)\n",
" 49 STORE_FAST 3 (word_length)\n",
"\n",
" 6 52 LOAD_FAST 1 (total)\n",
" 55 LOAD_FAST 3 (word_length)\n",
" 58 INPLACE_ADD\n",
" 59 LOAD_CONST 4 (<function log.<locals>.<lambda> at 0x106bfde18>)\n",
" 62 ROT_TWO\n",
" 63 CALL_FUNCTION 1 (1 positional, 0 keyword pair)\n",
" 66 STORE_FAST 1 (total)\n",
" 69 JUMP_ABSOLUTE 20\n",
" >> 72 POP_BLOCK\n",
"\n",
" 7 >> 73 LOAD_FAST 1 (total)\n",
" 76 RETURN_VALUE\n"
]
}
],
"source": [
"dis(sum_word_lengths)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## OrderedDict Literals"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"from codetransformer.transformers.literals import ordereddict_literals"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"OrderedDict([('a', 1), ('b', 2), ('c', 3)])"
]
},
"execution_count": 14,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"@ordereddict_literals\n",
"def foo(a, b, c):\n",
" return {'a': a, 'b': b, 'c': c}\n",
"\n",
"foo(1, 2, 3)"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" 1 0 LOAD_CONST 0 (<class 'collections.OrderedDict'>)\n",
" 3 CALL_FUNCTION 0 (0 positional, 0 keyword pair)\n",
" 6 DUP_TOP\n",
" 7 DUP_TOP\n",
" 8 DUP_TOP\n",
" 9 LOAD_FAST 0 (a)\n",
" 12 LOAD_CONST 1 ('a')\n",
" 15 ROT_THREE\n",
" 16 ROT_THREE\n",
" 17 ROT_TWO\n",
" 18 STORE_SUBSCR\n",
" 19 LOAD_FAST 1 (b)\n",
" 22 LOAD_CONST 2 ('b')\n",
" 25 ROT_THREE\n",
" 26 ROT_THREE\n",
" 27 ROT_TWO\n",
" 28 STORE_SUBSCR\n",
" 29 LOAD_FAST 2 (c)\n",
" 32 LOAD_CONST 3 ('c')\n",
" 35 ROT_THREE\n",
" 36 ROT_THREE\n",
" 37 ROT_TWO\n",
" 38 STORE_SUBSCR\n",
" 39 RETURN_VALUE\n"
]
}
],
"source": [
"dis(foo)"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"OrderedDict([(0, 1), (1, 2), (2, 3), (3, 4), (4, 5)])"
]
},
"execution_count": 16,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"@ordereddict_literals\n",
"def foo(data):\n",
" return {i: i + 1 for i in data}\n",
"\n",
"foo(range(5))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## SymPy Literals"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"from codetransformer.transformers.literals import overloaded_constants\n",
"from sympy import symbols, Integer"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"sympy_ints = overloaded_constants(int)(Integer)"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"a, b = symbols('a, b')"
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"0.111111111111111*a + b"
]
},
"execution_count": 20,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"c = 3\n",
"a*(1/c)**2 + b"
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"@sympy_ints\n",
"def foo(a, b):\n",
" c = 3\n",
" return a*(1/c)**2 + b"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"a/9 + b"
]
},
"execution_count": 22,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"foo(a, b)"
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" 3 0 LOAD_CONST 0 (3)\n",
" 3 STORE_FAST 4 (c)\n",
"\n",
" 4 6 LOAD_FAST 0 (a)\n",
" 9 LOAD_CONST 1 (1)\n",
" 12 LOAD_FAST 4 (c)\n",
" 15 BINARY_TRUE_DIVIDE\n",
" 16 LOAD_CONST 2 (2)\n",
" 19 BINARY_POWER\n",
" 20 BINARY_MULTIPLY\n",
" 21 LOAD_FAST 1 (b)\n",
" 24 BINARY_ADD\n",
" 25 RETURN_VALUE\n"
]
}
],
"source": [
"dis(foo)"
]
},
{
"cell_type": "code",
"execution_count": 24,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"3 is of type <class 'sympy.core.numbers.Integer'>\n",
"1 is of type <class 'sympy.core.numbers.One'>\n",
"2 is of type <class 'sympy.core.numbers.Integer'>\n"
]
}
],
"source": [
"for const in foo.__code__.co_consts:\n",
" print('{0} is of type {1}'.format(const, type(const)))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## Micro-optimization - LOAD_GLOBAL -> LOAD_CONST"
]
},
{
"cell_type": "code",
"execution_count": 25,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"from codetransformer.transformers import asconstants"
]
},
{
"cell_type": "code",
"execution_count": 26,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"f = lambda a: a + 1\n",
"g = lambda b: b * 2\n",
"h = lambda c: c - 1\n",
"\n",
"def foo(data):\n",
" total = 0\n",
" for i in data:\n",
" total += f(g(h(i)))\n",
" return total\n",
" \n",
"foo2 = asconstants(g=g, f=f, h=h)(foo)"
]
},
{
"cell_type": "code",
"execution_count": 27,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"100 loops, best of 3: 3.71 ms per loop\n",
"100 loops, best of 3: 3.45 ms per loop\n"
]
}
],
"source": [
"%timeit foo(range(10000))\n",
"%timeit foo2(range(10000))"
]
},
{
"cell_type": "code",
"execution_count": 28,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" 6 0 LOAD_CONST 1 (0)\n",
" 3 STORE_FAST 1 (total)\n",
"\n",
" 7 6 SETUP_LOOP 42 (to 51)\n",
" 9 LOAD_FAST 0 (data)\n",
" 12 GET_ITER\n",
" >> 13 FOR_ITER 34 (to 50)\n",
" 16 STORE_FAST 2 (i)\n",
"\n",
" 8 19 LOAD_FAST 1 (total)\n",
" 22 LOAD_GLOBAL 0 (f)\n",
" 25 LOAD_GLOBAL 1 (g)\n",
" 28 LOAD_GLOBAL 2 (h)\n",
" 31 LOAD_FAST 2 (i)\n",
" 34 CALL_FUNCTION 1 (1 positional, 0 keyword pair)\n",
" 37 CALL_FUNCTION 1 (1 positional, 0 keyword pair)\n",
" 40 CALL_FUNCTION 1 (1 positional, 0 keyword pair)\n",
" 43 INPLACE_ADD\n",
" 44 STORE_FAST 1 (total)\n",
" 47 JUMP_ABSOLUTE 13\n",
" >> 50 POP_BLOCK\n",
"\n",
" 9 >> 51 LOAD_FAST 1 (total)\n",
" 54 RETURN_VALUE\n"
]
}
],
"source": [
"dis(foo)"
]
},
{
"cell_type": "code",
"execution_count": 29,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" 6 0 LOAD_CONST 0 (0)\n",
" 3 STORE_FAST 3 (total)\n",
"\n",
" 7 6 SETUP_LOOP 42 (to 51)\n",
" 9 LOAD_FAST 0 (data)\n",
" 12 GET_ITER\n",
" >> 13 FOR_ITER 34 (to 50)\n",
" 16 STORE_FAST 2 (i)\n",
"\n",
" 8 19 LOAD_FAST 3 (total)\n",
" 22 LOAD_CONST 1 (<function <lambda> at 0x1086a9048>)\n",
" 25 LOAD_CONST 2 (<function <lambda> at 0x1086a90d0>)\n",
" 28 LOAD_CONST 3 (<function <lambda> at 0x1086a9158>)\n",
" 31 LOAD_FAST 2 (i)\n",
" 34 CALL_FUNCTION 1 (1 positional, 0 keyword pair)\n",
" 37 CALL_FUNCTION 1 (1 positional, 0 keyword pair)\n",
" 40 CALL_FUNCTION 1 (1 positional, 0 keyword pair)\n",
" 43 INPLACE_ADD\n",
" 44 STORE_FAST 3 (total)\n",
" 47 JUMP_ABSOLUTE 13\n",
" >> 50 POP_BLOCK\n",
"\n",
" 9 >> 51 LOAD_FAST 3 (total)\n",
" 54 RETURN_VALUE\n"
]
}
],
"source": [
"dis(foo2)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"# Crazyness"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Inline Type Assertion Operator `<~`"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Similar to Julia's `::` operator."
]
},
{
"cell_type": "code",
"execution_count": 30,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"def type_error(var, typ):\n",
" return TypeError(\"Expected type '{0}', got '{1}'\".format(\n",
" typ.__name__, type(var).__name__))\n",
"\n",
"\n",
"class TypeAsserts(CodeTransformer):\n",
" def __init__(self):\n",
" super().__init__()\n",
"\n",
" @pattern(ir.UNARY_INVERT, ir.COMPARE_OP)\n",
" def _left_wavy_arrow(self, instr1, instr2):\n",
" if instr2.arg != 0: # LT\n",
" yield instr1\n",
" yield instr2\n",
" else:\n",
" yield ir.STORE_FAST('___typ_rhs').steal(instr1).steal(instr2)\n",
" yield ir.STORE_FAST('___typ_lhs')\n",
" yield ir.LOAD_CONST(isinstance)\n",
" yield ir.LOAD_FAST('___typ_lhs')\n",
" yield ir.LOAD_FAST('___typ_rhs')\n",
" yield ir.CALL_FUNCTION(positional=2)\n",
" jmp = ir.POP_JUMP_IF_TRUE(0)\n",
" yield jmp\n",
" yield ir.LOAD_CONST(type_error)\n",
" yield ir.LOAD_FAST('___typ_lhs')\n",
" yield ir.LOAD_FAST('___typ_rhs')\n",
" yield ir.CALL_FUNCTION(positional=2)\n",
" yield ir.RAISE_VARARGS(1)\n",
" jmp.arg = target = ir.LOAD_FAST('___typ_lhs')\n",
" target._target_of.add(jmp)\n",
" yield target\n",
" \n",
"type_asserts = TypeAsserts()"
]
},
{
"cell_type": "code",
"execution_count": 31,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"@type_asserts\n",
"def foo(a, b):\n",
" c = ((a + b) <~ int) + 1\n",
" return c + 1"
]
},
{
"cell_type": "code",
"execution_count": 32,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"5"
]
},
"execution_count": 32,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"foo(1, 2)"
]
},
{
"cell_type": "code",
"execution_count": 33,
"metadata": {
"collapsed": false
},
"outputs": [
{
"ename": "TypeError",
"evalue": "Expected type 'int', got 'float'",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-33-2ecacc04ce01>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mfoo\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;36m2.\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m",
"\u001b[0;32m<ipython-input-31-70955ecf7b98>\u001b[0m in \u001b[0;36mfoo\u001b[0;34m(a, b)\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;34m@\u001b[0m\u001b[0mtype_asserts\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 2\u001b[0m \u001b[0;32mdef\u001b[0m \u001b[0mfoo\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0ma\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mb\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 3\u001b[0;31m \u001b[0mc\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0ma\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0mb\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m<\u001b[0m\u001b[0;34m~\u001b[0m \u001b[0mint\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 4\u001b[0m \u001b[0;32mreturn\u001b[0m \u001b[0mc\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;31mTypeError\u001b[0m: Expected type 'int', got 'float'"
]
}
],
"source": [
"foo(1, 2.)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## Pattern Matched Exceptions"
]
},
{
"cell_type": "code",
"execution_count": 34,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"from codetransformer.transformers.patterns import pattern_matched_exceptions"
]
},
{
"cell_type": "code",
"execution_count": 35,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"def bar(x, y):\n",
" if x > y:\n",
" raise ValueError('Greater')\n",
" elif x < 0:\n",
" raise ValueError('LessThan')\n",
" else:\n",
" raise ValueError('Equal')\n",
"\n",
"@pattern_matched_exceptions()\n",
"def foo(x):\n",
" try:\n",
" bar(x, 0)\n",
" except ValueError('Greater'):\n",
" print('Positive Number')\n",
" except ValueError('LessThan'):\n",
" print('Negative Number')\n",
" except ValueError('Equal'):\n",
" print('Zero')"
]
},
{
"cell_type": "code",
"execution_count": 36,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Positive Number\n",
"Negative Number\n",
"Zero\n"
]
}
],
"source": [
"foo(1)\n",
"foo(-1)\n",
"foo(0)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## Overload `is`"
]
},
{
"cell_type": "code",
"execution_count": 37,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"def eval_is(a, b):\n",
" if hasattr(a, '__is__'):\n",
" return a.__is__(b)\n",
" elif hasattr(b, '__ris__'):\n",
" return b.__ris__(a)\n",
" return a is b\n",
"\n",
"\n",
"class overload_is(CodeTransformer):\n",
" def __init__(self, eval_is=eval_is):\n",
" super().__init__()\n",
" self._eval_is = eval_is\n",
"\n",
" @pattern(ir.COMPARE_OP)\n",
" def _compare_op(self, instr):\n",
" if instr.arg != 8: # IS\n",
" yield instr\n",
" else:\n",
" yield ir.LOAD_CONST(self._eval_is).steal(instr)\n",
" yield ir.ROT_THREE()\n",
" yield ir.CALL_FUNCTION(positional=2)"
]
},
{
"cell_type": "code",
"execution_count": 38,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"class IsAllThings(object):\n",
" def __is__(self, other):\n",
" print(\"__is__ called\")\n",
" return True\n",
" \n",
" def __ris__(self, other):\n",
" print(\"__ris__ called\")\n",
" return True"
]
},
{
"cell_type": "code",
"execution_count": 39,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"__is__ called\n",
"True\n",
"__ris__ called\n",
"True\n"
]
}
],
"source": [
"@overload_is()\n",
"def bar(a, b):\n",
" print(a is b)\n",
" print(b is a)\n",
" \n",
"bar(IsAllThings(), 1)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"---\n",
"\n",
"## Goto Statement"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Implemented already in https://github.com/snoack/python-goto, but they just insert a bunch of `NOP` instructions. We can clean up better than that :)"
]
},
{
"cell_type": "code",
"execution_count": 40,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"image/png": [
"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\n"
],
"text/plain": [
"<IPython.core.display.Image object>"
]
},
"execution_count": 40,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from IPython.display import Image\n",
"Image('https://imgs.xkcd.com/comics/goto.png')"
]
},
{
"cell_type": "code",
"execution_count": 41,
"metadata": {
"collapsed": false
},
"outputs": [],
"source": [
"from codetransformer.patterns import matchany, var\n",
"\n",
"class with_goto(CodeTransformer):\n",
" def __init__(self):\n",
" super().__init__()\n",
" self._gotos = {}\n",
" self._labels = {}\n",
"\n",
" @pattern(ir.LOAD_GLOBAL, ir.LOAD_ATTR, ir.POP_TOP, matchany[var])\n",
" def _goto(self, instr1, instr2, instr3, *instrs):\n",
" if instr1.arg == 'goto':\n",
" jmp = ir.JUMP_ABSOLUTE(0).steal(instr1).steal(instr2).steal(instr3)\n",
" self._gotos.setdefault(instr2.arg, []).append(jmp)\n",
" yield jmp\n",
" elif instr1.arg == 'label':\n",
" target, instrs = instrs[0], instrs[1:]\n",
" target.steal(instr1).steal(instr2).steal(instr3)\n",
" self._labels[instr2.arg] = target\n",
" yield target\n",
" else:\n",
" yield instr1\n",
" yield instr2\n",
" yield instr3\n",
" yield from self.patterndispatcher(instrs)\n",
" # Cleanup labels\n",
" for label, target in self._labels.items():\n",
" for jmp in self._gotos[label]:\n",
" jmp.arg = target\n",
" target._target_of.add(jmp)"
]
},
{
"cell_type": "code",
"execution_count": 42,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"@with_goto()\n",
"def range(n):\n",
" i = 0\n",
"\n",
" label .begin\n",
"\n",
" if i == n:\n",
" goto .end\n",
"\n",
" yield i\n",
" i += 1\n",
"\n",
" goto .begin\n",
"\n",
" label .end"
]
},
{
"cell_type": "code",
"execution_count": 43,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]"
]
},
"execution_count": 43,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"list(range(10))"
]
},
{
"cell_type": "code",
"execution_count": 44,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" 3 0 LOAD_CONST 0 (0)\n",
" 3 STORE_FAST 1 (i)\n",
"\n",
" 7 >> 6 LOAD_FAST 1 (i)\n",
" 9 LOAD_FAST 0 (n)\n",
" 12 COMPARE_OP 2 (==)\n",
" 15 POP_JUMP_IF_FALSE 24\n",
" 18 JUMP_ABSOLUTE 42\n",
" 21 JUMP_FORWARD 0 (to 24)\n",
"\n",
" 10 >> 24 LOAD_FAST 1 (i)\n",
" 27 YIELD_VALUE\n",
" 28 POP_TOP\n",
"\n",
" 11 29 LOAD_FAST 1 (i)\n",
" 32 LOAD_CONST 1 (1)\n",
" 35 INPLACE_ADD\n",
" 36 STORE_FAST 1 (i)\n",
" 39 JUMP_ABSOLUTE 6\n",
" >> 42 LOAD_CONST 2 (None)\n",
" 45 RETURN_VALUE\n"
]
}
],
"source": [
"dis(range)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"If your numba code doesn't feel low level enough..."
]
},
{
"cell_type": "code",
"execution_count": 45,
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]"
]
},
"execution_count": 45,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from numba import jit\n",
"\n",
"# Jit a function with gotos\n",
"range = jit(nopython=True)(range)\n",
"\n",
"list(range(10))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Segfaults!"
]
},
{
"cell_type": "code",
"execution_count": 46,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"@with_goto()\n",
"def works_fine():\n",
" for i in [1, 2, 3]:\n",
" goto .out_of_loop\n",
" label .back_in_loop\n",
" return\n",
" label .out_of_loop\n",
" print(i)\n",
" goto .back_in_loop"
]
},
{
"cell_type": "code",
"execution_count": 47,
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"1\n",
"2\n",
"3\n"
]
}
],
"source": [
"works_fine()"
]
},
{
"cell_type": "code",
"execution_count": 48,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"@with_goto()\n",
"def segfaults():\n",
" for i in [1, 2, 3]:\n",
" goto .out_of_loop\n",
" label .back_in_loop\n",
" #return <- oop, forgot the return\n",
" label .out_of_loop\n",
" print(i)\n",
" goto .back_in_loop"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": [
"segfaults()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Other ideas:\n",
"\n",
"- Inline function bytecode\n",
"- Compilation of functional style pipelining\n",
"- OpenMP style parallel for loop pragmas?"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.4.3"
}
},
"nbformat": 4,
"nbformat_minor": 0
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment