SlideShare a Scribd company logo
Коварный CodeType,
или от segfault'а
к работающему коду
Андрей Захаревич
Дмитрий Алимов
С чего все началось
# Plain functions
if inspect.isroutine(obj):
if inspect.ismethod(obj):
has_self = True
argspec = inspect.getargspec(obj)
# Class constructors
elif inspect.isclass(obj):
try:
argspec = inspect.getargspec(obj.__new__)
except (AttributeError, TypeError):
argspec = inspect.getargspec(obj.__init__)
has_self = True
# obj objects
elif hasattr(obj, '__call__'):
method = obj.__call__
argspec = inspect.getargspec(method)
has_self = inspect.ismethod(method)
return argspec.args[1:] if has_self else args
Проблема
● Моя функция принимает переменное число аргументов (*args)
● Число передаваемых аргументов я узнаю в рантайме
● Объект функции в это время уже создан
Что же делать?
Метапрограммирование!
Ленивый вариант
compile('def func(a, b): f(a, b)', 'func.py', 'exec')
Не спортивно
Идем на stackoverflow
import types
def create_function(name, args):
def y(): pass
y_code = types.CodeType(args,
y.func_code.co_nlocals,
y.func_code.co_stacksize,
y.func_code.co_flags,
y.func_code.co_code,
y.func_code.co_consts,
y.func_code.co_names,
y.func_code.co_varnames,
y.func_code.co_filename,
name,
y.func_code.co_firstlineno,
y.func_code.co_lnotab)
return types.FunctionType(y_code, y.func_globals, name)
myfunc = create_function('myfunc', 3)
myfunc(1, 2, 3, 4)
# TypeError: myfunc() takes exactly 3 arguments (4 given)
Кажется должно работать
def create_function(name, nargs):
def y(*args):
print args
y_code = types.CodeType(nargs,
y.func_code.co_nlocals,
# Параметры
)
return types.FunctionType(y_code, y.func_globals,
name)
myfunc = create_function('myfunc', 3)
myfunc(1, 2, 3)
Но нет
[1] 8514 segmentation fault python func.py
Еще попытка
class Meta(type):
def __new__(mcls, name, bases, attrs):
nargs = 3
varnames = tuple(['self'] + [str(i) for i in range(nargs)])
ret_code = types.CodeType(
nargs + 1,
attrs['ret_func'].func_code.co_nlocals,
attrs['ret_func'].func_code.co_stacksize,
attrs['ret_func'].func_code.co_flags,
attrs['ret_func'].func_code.co_code,
attrs['ret_func'].func_code.co_consts,
attrs['ret_func'].func_code.co_names,
varnames,
attrs['ret_func'].func_code.co_filename,
'call',
attrs['ret_func'].func_code.co_firstlineno,
attrs['ret_func'].func_code.co_lnotab)
attrs['call'] = types.FunctionType(
ret_code,
attrs['ret_func'].func_globals, 'call')
return super(Meta, mcls).__new__(mcls, name, bases, attrs)
class CustomCall(object):
__metaclass__ = Meta
def __call__(self, *args):
self.call(*args)
def ret_func(self):
print self.call.__name__
obj = CustomCall()
obj.call(1, 2, 3)
# Если
закомментировать, то
все упадет
obj(1, 2, 3)
Еще попытка
class Meta(type):
def __new__(mcls, name, bases, attrs):
nargs = 3
varnames = tuple(['self'] + [str(i) for i in range(nargs)])
ret_code = types.CodeType(
nargs + 1,
attrs['ret_func'].func_code.co_nlocals,
attrs['ret_func'].func_code.co_stacksize,
attrs['ret_func'].func_code.co_flags,
attrs['ret_func'].func_code.co_code,
attrs['ret_func'].func_code.co_consts,
attrs['ret_func'].func_code.co_names,
varnames,
attrs['ret_func'].func_code.co_filename,
'call',
attrs['ret_func'].func_code.co_firstlineno,
attrs['ret_func'].func_code.co_lnotab)
attrs['call'] = types.FunctionType(
ret_code,
attrs['ret_func'].func_globals, 'call')
return super(Meta, mcls).__new__(mcls, name, bases, attrs)
class CustomCall(object):
__metaclass__ = Meta
def __call__(self, *args):
self.call(*args)
def ret_func(self):
print self.call.__name__
obj = CustomCall()
obj.call(1, 2, 3)
# Если
закомментировать, то
все упадет
obj(1, 2, 3)
Заглянем в Disassembler
5D09E429 |. 8B7424 10 MOV ESI,DWORD PTR SS:[ESP+10] ; ESI = frame
5D09E42D |. 8B7D 14 MOV EDI,DWORD PTR SS:[EBP+14] ; EDI = *args
5D09E430 |. 8B5C24 18 MOV EBX,DWORD PTR SS:[ESP+18] ; EBX = argcount
5D09E434 |. 81C6 38010000 ADD ESI,0x138 ; ESI += 0x138 (f_localsplus)
5D09E43A |. 2BFE SUB EDI,ESI ; EDI -= ESI
5D09E43C |. 8D6424 00 LEA ESP,[ESP] ; NOP
5D09E440 |> 8B0437 /MOV EAX,DWORD PTR DS:[ESI+EDI] ; EAX = *(ESI + EDI)
5D09E443 |. FF00 |INC DWORD PTR DS:[EAX] ; Py_INCREF(*EAX)
5D09E445 |. 8B0E |MOV ECX,DWORD PTR DS:[ESI] ; ECX = *ESI
5D09E447 |. 8906 |MOV DWORD PTR DS:[ESI],EAX ; *ESI = EAX
5D09E449 |. 85C9 |TEST ECX,ECX ; test ECX:
5D09E44B |.- 74 11 |JZ SHORT 5D09E45E ; if 0: goto 5D09E45E
5D09E44D |. 8301 FF |ADD DWORD PTR DS:[ECX],-1 ; else: Py_XDECREF(*ECX)
5D09E450 |.- 75 0C |JNZ SHORT 5D09E45E ; if not 0: goto 5D09E45E
5D09E452 |. 8B41 04 |MOV EAX,DWORD PTR DS:[ECX+4] ; ECX->ob_type
5D09E455 |. 51 |PUSH ECX ; save fastlocals[i] to stack
5D09E456 |. 8B48 18 |MOV ECX,DWORD PTR DS:[EAX+18] ; ECX = ob_type->tp_dealloc
5D09E459 |. FFD1 |CALL ECX ; call ob_type->tp_dealloc()
5D09E45B |. 83C4 04 |ADD ESP,4 ; restore ESP
5D09E45E |> 83C6 04 |ADD ESI,4 ; ESI += 4
5D09E461 |. 83EB 01 |SUB EBX,1 ; EBX -= 1
5D09E464 |.- 75 DA JNZ SHORT 5D09E440 ;
01eb3030 00000001 ob_refcnt
01eb3034 5886d830 python27!PyFrame_Type
01eb3038 00000002 ob_size
01eb303c 023b6b30 f_back
01eb3040 023a6b18 f_code
01eb3044 01e790c0 f_builtins
01eb3048 01e8aa50 f_globals
01eb304c 00000000 f_locals
01eb3050 01eb316c f_valuestack
01eb3054 01eb316c f_stacktop
01eb3058 00000000 f_trace
01eb305c 00000000 f_exc_type
01eb3060 00000000 f_exc_value
01eb3064 00000000 f_exc_traceback
01eb3068 01f72cf0 f_tstate
01eb306c ffffffff f_lasti
01eb3070 0000002f f_lineno
01eb3074 00000000 f_iblock
01eb3078 baadf00d f_blockstack[20] = {b_type,
01eb307c baadf00d b_handler
01eb3080 baadf00d b_level}
...
01eb3168 023bf550 f_localsplus // frame + 0x138
01eb316c 01f7d8a0
01eb3170 baadf00d
01eb3174 baadf00d
Что в памяти
PyObject *
PyEval_EvalCodeEx(PyCodeObject *co, PyObject *globals, PyObject *locals,
PyObject **args, int argcount, PyObject **kws, int kwcount,
PyObject **defs, int defcount, PyObject *closure) {
...
n = co->co_argcount;
for (i = 0; i < n; i++) {
x = args[i];
Py_INCREF(x);
SETLOCAL(i, x);
}
...
https://github.com/python/cpython/blob/2.7/Python/ceval.c
#define Py_INCREF(op) ( 
_Py_INC_REFTOTAL _Py_REF_DEBUG_COMMA 
((PyObject*)(op))->ob_refcnt++)
#define GETLOCAL(i) (fastlocals[i])
#define SETLOCAL(i, value) do { PyObject *tmp = GETLOCAL(i); 
GETLOCAL(i) = value; 
Py_XDECREF(tmp); } while (0)
#define Py_XDECREF(op) do { if ((op) == NULL); else Py_DECREF(op); } while (0)
#define Py_DECREF(op) 
do { 
if (_Py_DEC_REFTOTAL _Py_REF_DEBUG_COMMA 
--((PyObject*)(op))->ob_refcnt != 0) 
_Py_CHECK_REFCNT(op) 
else 
_Py_Dealloc((PyObject *)(op)); 
} while (0)
#define _Py_Dealloc(op) ( 
_Py_INC_TPFREES(op) _Py_COUNT_ALLOCS_COMMA 
(*Py_TYPE(op)->tp_dealloc)((PyObject *)(op)))
#endif /* !Py_TRACE_REFS */
https://github.com/python/cpython/blob/2.7/Include/object.h
typedef struct _frame {
PyObject_VAR_HEAD
struct _frame *f_back; /* previous frame, or NULL */
PyCodeObject *f_code; /* code segment */
PyObject *f_builtins; /* builtin symbol table (PyDictObject) */
PyObject *f_globals; /* global symbol table (PyDictObject) */
PyObject *f_locals; /* local symbol table (any mapping) */
PyObject **f_valuestack; /* points after the last local */
PyObject **f_stacktop;
PyObject *f_trace; /* Trace function */
PyObject *f_exc_type, *f_exc_value, *f_exc_traceback;
PyThreadState *f_tstate;
int f_lasti; /* Last instruction if called */
int f_lineno; /* Current line number */
int f_iblock; /* index in f_blockstack */
PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
PyObject *f_localsplus[1]; /* locals+stack, dynamically sized */
} PyFrameObject;
https://github.com/python/cpython/blob/2.7/Include/frameobject.h
ncells = PyTuple_GET_SIZE(code->co_cellvars);
nfrees = PyTuple_GET_SIZE(code->co_freevars);
extras = code->co_stacksize + code->co_nlocals + ncells + nfrees;
if (free_list == NULL) {
f = PyObject_GC_NewVar(PyFrameObject, &PyFrame_Type, extras);
...
https://github.com/python/cpython/blob/2.7/Objects/frameobject.c
co_cellvars – is a tuple with the names of local variables referenced by nested functions;
co_freevars – names of variables in the function, defined in an enclosing function scope;
Память для f_localsplus
Проблема найдена
ret_code = types.CodeType(
nargs + 1,
attrs['ret_func'].func_code.co_nlocals,
attrs['ret_func'].func_code.co_stacksize,
attrs['ret_func'].func_code.co_flags,
attrs['ret_func'].func_code.co_code,
attrs['ret_func'].func_code.co_consts,
attrs['ret_func'].func_code.co_names,
varnames,
attrs['ret_func'].func_code.co_filename,
'call',
attrs['ret_func'].func_code.co_firstlineno,
attrs['ret_func'].func_code.co_lnotab,
(), # freevars
() # cellvars
)
Попробуем применить
def create_func(fun, nargs):
def wrapper():
args = map(itemgetter(1),
sorted(((var_name, arg) for (var_name, arg)
in locals().items() if var_name != 'fun'),
key=itemgetter(0)))
fun(*args)
varnames = tuple(str(i) for i in range(0, nargs))
ret_code = types.CodeType(nargs,
wrapper.func_code.co_nlocals + nargs,
# Параметры
)
return types.FunctionType(ret_code, wrapper.func_globals,
wrapper.func_code.co_name)
def test_func(*args):
print args
fun = create_func(test_func, 3)
fun(1, 2, 3)
Как-то не очень
Traceback (most recent call last):
File "func.py", line 38, in <module>
fun(1,2,3)
File "func.py", line 16, in wrapper
fun(*args)
SystemError: Objects/cellobject.c:24: bad argument to internal function
Заработало!
def create_func(fun, nargs):
def wrapper(fun):
args = map(itemgetter(1),
sorted(((var_name, arg) for (var_name, arg)
in locals().items() if var_name != 'fun'),
key=itemgetter(0)))
fun(*args)
varnames = tuple(['fun'] + [str(i) for i in range(0, nargs)])
ret_code = types.CodeType(nargs + 1,
wrapper.func_code.co_nlocals + nargs,
# Параметры
)
return partial(types.FunctionType(ret_code, wrapper.func_globals,
wrapper.func_code.co_name), fun)
def test_func(*args):
print args
fun = create_func(test_func, 3)
fun(1, 2, 3)
Что-то потеряли
>>> dir(wrapper.func_code)
['__class__', '__cmp__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__',
'__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__ne__', '__new__',
'__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__',
'__subclasshook__', 'co_argcount', 'co_cellvars', 'co_code', 'co_consts', 'co_filename',
'co_firstlineno', 'co_flags', 'co_freevars', 'co_lnotab', 'co_name', 'co_names', 'co_nlocals',
'co_stacksize', 'co_varnames']
Добавим параметр
ret_code = types.CodeType(nargs,
wrapper.func_code.co_nlocals + nargs,
# Параметры
wrapper.func_code.co_freevars)
return types.FunctionType(ret_code, wrapper.func_globals,
wrapper.func_code.co_name)
>>>Traceback (most recent call last):
File "func.py", line 39, in <module>
fun = create_func(test_func, 3)
File "func.py", line 33, in create_func
return types.FunctionType(ret_code, wrapper.func_globals,
wrapper.func_code.co_name)
TypeError: arg 5 (closure) must be tuple
def create_func(fun, nargs):
def wrapper():
args = map(itemgetter(1),
sorted(((var_name, arg) for (var_name, arg)
in locals().items() if var_name != 'fun'),
key=itemgetter(0)))
fun(*args)
varnames = tuple([str(i) for i in range(0, nargs)])
ret_code = types.CodeType(nargs,
wrapper.func_code.co_nlocals + nargs,
# Параметры
wrapper.func_code.co_freevars)
return types.FunctionType(ret_code, wrapper.func_globals,
wrapper.func_code.co_name, None,
wrapper.func_closure)
def test_func(*args):
print args
fun = create_func(test_func, 3)
fun(1, 2, 3)
И наконец, замыкания работают!
… почти
from operator import itemgetter
def wrapper():
print locals()
args = map(itemgetter(1),
sorted(((var_name, arg) for (var_name, arg)
in locals().items() if var_name != 'fun'),
key=itemgetter(0)))
print locals()
fun(*args)
{'1': 2, '0': 1, '2': 3, 'fun': <function test_func at 0x10cd62398>}
{'1': 2, '0': [1, 2, 3], '2': 3, 'fun': <function test_func at 0x10cd62398>}
Спасибо за внимание!
Вопросы?
SPb Python Interest Group
https://telegram.me/spbpython

More Related Content

PDF
ITGM #9 - Коварный CodeType, или от segfault'а к работающему коду
PPT
Евгений Крутько, Многопоточные вычисления, современный подход.
PDF
Алексей Кутумов, Coroutines everywhere
PDF
Антон Бикинеев, Writing good std::future&lt; C++ >
PDF
Clang tidy
PPTX
Lexical environment in ecma 262 5
KEY
連邦の白いヤツ 「Objective-C」
PDF
4. Обработка ошибок, исключения, отладка
ITGM #9 - Коварный CodeType, или от segfault'а к работающему коду
Евгений Крутько, Многопоточные вычисления, современный подход.
Алексей Кутумов, Coroutines everywhere
Антон Бикинеев, Writing good std::future&lt; C++ >
Clang tidy
Lexical environment in ecma 262 5
連邦の白いヤツ 「Objective-C」
4. Обработка ошибок, исключения, отладка

What's hot (20)

PPT
Whats new in_csharp4
PDF
The Evolution of Async-Programming (SD 2.0, JavaScript)
PDF
The Ring programming language version 1.5.3 book - Part 88 of 184
PDF
One definition rule - что это такое, и как с этим жить
PDF
Metaprogramming and Reflection in Common Lisp
PDF
Welcome to Modern C++
PPTX
Pro typescript.ch03.Object Orientation in TypeScript
PPTX
Namespaces
PDF
Java, Up to Date Sources
PPTX
Basic C++ 11/14 for Python Programmers
PDF
响应式编程及框架
PPTX
Самые вкусные баги из игрового кода: как ошибаются наши коллеги-программисты ...
PDF
Writing good std::future&lt;c++>
PDF
Message in a bottle
PDF
Javascript Uncommon Programming
PPT
E:\Plp 2009 2\Plp 9
PPTX
Node.js behind: V8 and its optimizations
PDF
Антон Бикинеев, Reflection in C++Next
PDF
第二回CTF勉強会資料
PDF
C++ L05-Functions
Whats new in_csharp4
The Evolution of Async-Programming (SD 2.0, JavaScript)
The Ring programming language version 1.5.3 book - Part 88 of 184
One definition rule - что это такое, и как с этим жить
Metaprogramming and Reflection in Common Lisp
Welcome to Modern C++
Pro typescript.ch03.Object Orientation in TypeScript
Namespaces
Java, Up to Date Sources
Basic C++ 11/14 for Python Programmers
响应式编程及框架
Самые вкусные баги из игрового кода: как ошибаются наши коллеги-программисты ...
Writing good std::future&lt;c++>
Message in a bottle
Javascript Uncommon Programming
E:\Plp 2009 2\Plp 9
Node.js behind: V8 and its optimizations
Антон Бикинеев, Reflection in C++Next
第二回CTF勉強会資料
C++ L05-Functions
Ad

Similar to Коварный code type ITGM #9 (20)

PDF
Bytes in the Machine: Inside the CPython interpreter
PDF
Improving Java performance at JBCNConf 2015
PDF
Improving Android Performance at Droidcon UK 2014
PDF
Analysis of Haiku Operating System (BeOS Family) by PVS-Studio. Part 2
PPTX
Как работает LLVM бэкенд в C#. Егор Богатов ➠ CoreHard Autumn 2019
PPT
OpenMP
PPT
Unit 4
PPTX
How to add an optimization for C# to RyuJIT
PPT
Bsides
 
PPTX
Introduction to Debuggers
PDF
Marat-Slides
PPTX
week14Pointers_II. pointers pemrograman dasar C++.pptx
PDF
삼성 바다 앱개발 실패 노하우 2부
KEY
10 Catalyst Tips
PPTX
Groovy
PDF
Hacking parse.y (RubyKansai38)
PDF
NYU hacknight, april 6, 2016
PDF
This is my code- #include -llvm-IR-LegacyPassManager-h- #include -llv.pdf
DOCX
C-Sharp Arithmatic Expression Calculator
Bytes in the Machine: Inside the CPython interpreter
Improving Java performance at JBCNConf 2015
Improving Android Performance at Droidcon UK 2014
Analysis of Haiku Operating System (BeOS Family) by PVS-Studio. Part 2
Как работает LLVM бэкенд в C#. Егор Богатов ➠ CoreHard Autumn 2019
OpenMP
Unit 4
How to add an optimization for C# to RyuJIT
Bsides
 
Introduction to Debuggers
Marat-Slides
week14Pointers_II. pointers pemrograman dasar C++.pptx
삼성 바다 앱개발 실패 노하우 2부
10 Catalyst Tips
Groovy
Hacking parse.y (RubyKansai38)
NYU hacknight, april 6, 2016
This is my code- #include -llvm-IR-LegacyPassManager-h- #include -llv.pdf
C-Sharp Arithmatic Expression Calculator
Ad

Recently uploaded (20)

PDF
Wondershare Filmora 15 Crack With Activation Key [2025
PDF
Understanding Forklifts - TECH EHS Solution
PPTX
CHAPTER 2 - PM Management and IT Context
PDF
Adobe Illustrator 28.6 Crack My Vision of Vector Design
PDF
Raksha Bandhan Grocery Pricing Trends in India 2025.pdf
PDF
Audit Checklist Design Aligning with ISO, IATF, and Industry Standards — Omne...
PPTX
ManageIQ - Sprint 268 Review - Slide Deck
PPTX
Agentic AI : A Practical Guide. Undersating, Implementing and Scaling Autono...
PPTX
Lecture 3: Operating Systems Introduction to Computer Hardware Systems
PDF
Which alternative to Crystal Reports is best for small or large businesses.pdf
PDF
Design an Analysis of Algorithms I-SECS-1021-03
PDF
Odoo Companies in India – Driving Business Transformation.pdf
PDF
System and Network Administraation Chapter 3
PPT
Introduction Database Management System for Course Database
PPTX
Agentic AI Use Case- Contract Lifecycle Management (CLM).pptx
PDF
Claude Code: Everyone is a 10x Developer - A Comprehensive AI-Powered CLI Tool
PDF
System and Network Administration Chapter 2
PDF
How to Migrate SBCGlobal Email to Yahoo Easily
PPTX
ai tools demonstartion for schools and inter college
PDF
top salesforce developer skills in 2025.pdf
Wondershare Filmora 15 Crack With Activation Key [2025
Understanding Forklifts - TECH EHS Solution
CHAPTER 2 - PM Management and IT Context
Adobe Illustrator 28.6 Crack My Vision of Vector Design
Raksha Bandhan Grocery Pricing Trends in India 2025.pdf
Audit Checklist Design Aligning with ISO, IATF, and Industry Standards — Omne...
ManageIQ - Sprint 268 Review - Slide Deck
Agentic AI : A Practical Guide. Undersating, Implementing and Scaling Autono...
Lecture 3: Operating Systems Introduction to Computer Hardware Systems
Which alternative to Crystal Reports is best for small or large businesses.pdf
Design an Analysis of Algorithms I-SECS-1021-03
Odoo Companies in India – Driving Business Transformation.pdf
System and Network Administraation Chapter 3
Introduction Database Management System for Course Database
Agentic AI Use Case- Contract Lifecycle Management (CLM).pptx
Claude Code: Everyone is a 10x Developer - A Comprehensive AI-Powered CLI Tool
System and Network Administration Chapter 2
How to Migrate SBCGlobal Email to Yahoo Easily
ai tools demonstartion for schools and inter college
top salesforce developer skills in 2025.pdf

Коварный code type ITGM #9

  • 1. Коварный CodeType, или от segfault'а к работающему коду Андрей Захаревич Дмитрий Алимов
  • 2. С чего все началось # Plain functions if inspect.isroutine(obj): if inspect.ismethod(obj): has_self = True argspec = inspect.getargspec(obj) # Class constructors elif inspect.isclass(obj): try: argspec = inspect.getargspec(obj.__new__) except (AttributeError, TypeError): argspec = inspect.getargspec(obj.__init__) has_self = True # obj objects elif hasattr(obj, '__call__'): method = obj.__call__ argspec = inspect.getargspec(method) has_self = inspect.ismethod(method) return argspec.args[1:] if has_self else args
  • 3. Проблема ● Моя функция принимает переменное число аргументов (*args) ● Число передаваемых аргументов я узнаю в рантайме ● Объект функции в это время уже создан Что же делать?
  • 5. Ленивый вариант compile('def func(a, b): f(a, b)', 'func.py', 'exec') Не спортивно
  • 6. Идем на stackoverflow import types def create_function(name, args): def y(): pass y_code = types.CodeType(args, y.func_code.co_nlocals, y.func_code.co_stacksize, y.func_code.co_flags, y.func_code.co_code, y.func_code.co_consts, y.func_code.co_names, y.func_code.co_varnames, y.func_code.co_filename, name, y.func_code.co_firstlineno, y.func_code.co_lnotab) return types.FunctionType(y_code, y.func_globals, name) myfunc = create_function('myfunc', 3) myfunc(1, 2, 3, 4) # TypeError: myfunc() takes exactly 3 arguments (4 given)
  • 7. Кажется должно работать def create_function(name, nargs): def y(*args): print args y_code = types.CodeType(nargs, y.func_code.co_nlocals, # Параметры ) return types.FunctionType(y_code, y.func_globals, name) myfunc = create_function('myfunc', 3) myfunc(1, 2, 3)
  • 8. Но нет [1] 8514 segmentation fault python func.py
  • 9. Еще попытка class Meta(type): def __new__(mcls, name, bases, attrs): nargs = 3 varnames = tuple(['self'] + [str(i) for i in range(nargs)]) ret_code = types.CodeType( nargs + 1, attrs['ret_func'].func_code.co_nlocals, attrs['ret_func'].func_code.co_stacksize, attrs['ret_func'].func_code.co_flags, attrs['ret_func'].func_code.co_code, attrs['ret_func'].func_code.co_consts, attrs['ret_func'].func_code.co_names, varnames, attrs['ret_func'].func_code.co_filename, 'call', attrs['ret_func'].func_code.co_firstlineno, attrs['ret_func'].func_code.co_lnotab) attrs['call'] = types.FunctionType( ret_code, attrs['ret_func'].func_globals, 'call') return super(Meta, mcls).__new__(mcls, name, bases, attrs) class CustomCall(object): __metaclass__ = Meta def __call__(self, *args): self.call(*args) def ret_func(self): print self.call.__name__ obj = CustomCall() obj.call(1, 2, 3) # Если закомментировать, то все упадет obj(1, 2, 3)
  • 10. Еще попытка class Meta(type): def __new__(mcls, name, bases, attrs): nargs = 3 varnames = tuple(['self'] + [str(i) for i in range(nargs)]) ret_code = types.CodeType( nargs + 1, attrs['ret_func'].func_code.co_nlocals, attrs['ret_func'].func_code.co_stacksize, attrs['ret_func'].func_code.co_flags, attrs['ret_func'].func_code.co_code, attrs['ret_func'].func_code.co_consts, attrs['ret_func'].func_code.co_names, varnames, attrs['ret_func'].func_code.co_filename, 'call', attrs['ret_func'].func_code.co_firstlineno, attrs['ret_func'].func_code.co_lnotab) attrs['call'] = types.FunctionType( ret_code, attrs['ret_func'].func_globals, 'call') return super(Meta, mcls).__new__(mcls, name, bases, attrs) class CustomCall(object): __metaclass__ = Meta def __call__(self, *args): self.call(*args) def ret_func(self): print self.call.__name__ obj = CustomCall() obj.call(1, 2, 3) # Если закомментировать, то все упадет obj(1, 2, 3)
  • 11. Заглянем в Disassembler 5D09E429 |. 8B7424 10 MOV ESI,DWORD PTR SS:[ESP+10] ; ESI = frame 5D09E42D |. 8B7D 14 MOV EDI,DWORD PTR SS:[EBP+14] ; EDI = *args 5D09E430 |. 8B5C24 18 MOV EBX,DWORD PTR SS:[ESP+18] ; EBX = argcount 5D09E434 |. 81C6 38010000 ADD ESI,0x138 ; ESI += 0x138 (f_localsplus) 5D09E43A |. 2BFE SUB EDI,ESI ; EDI -= ESI 5D09E43C |. 8D6424 00 LEA ESP,[ESP] ; NOP 5D09E440 |> 8B0437 /MOV EAX,DWORD PTR DS:[ESI+EDI] ; EAX = *(ESI + EDI) 5D09E443 |. FF00 |INC DWORD PTR DS:[EAX] ; Py_INCREF(*EAX) 5D09E445 |. 8B0E |MOV ECX,DWORD PTR DS:[ESI] ; ECX = *ESI 5D09E447 |. 8906 |MOV DWORD PTR DS:[ESI],EAX ; *ESI = EAX 5D09E449 |. 85C9 |TEST ECX,ECX ; test ECX: 5D09E44B |.- 74 11 |JZ SHORT 5D09E45E ; if 0: goto 5D09E45E 5D09E44D |. 8301 FF |ADD DWORD PTR DS:[ECX],-1 ; else: Py_XDECREF(*ECX) 5D09E450 |.- 75 0C |JNZ SHORT 5D09E45E ; if not 0: goto 5D09E45E 5D09E452 |. 8B41 04 |MOV EAX,DWORD PTR DS:[ECX+4] ; ECX->ob_type 5D09E455 |. 51 |PUSH ECX ; save fastlocals[i] to stack 5D09E456 |. 8B48 18 |MOV ECX,DWORD PTR DS:[EAX+18] ; ECX = ob_type->tp_dealloc 5D09E459 |. FFD1 |CALL ECX ; call ob_type->tp_dealloc() 5D09E45B |. 83C4 04 |ADD ESP,4 ; restore ESP 5D09E45E |> 83C6 04 |ADD ESI,4 ; ESI += 4 5D09E461 |. 83EB 01 |SUB EBX,1 ; EBX -= 1 5D09E464 |.- 75 DA JNZ SHORT 5D09E440 ;
  • 12. 01eb3030 00000001 ob_refcnt 01eb3034 5886d830 python27!PyFrame_Type 01eb3038 00000002 ob_size 01eb303c 023b6b30 f_back 01eb3040 023a6b18 f_code 01eb3044 01e790c0 f_builtins 01eb3048 01e8aa50 f_globals 01eb304c 00000000 f_locals 01eb3050 01eb316c f_valuestack 01eb3054 01eb316c f_stacktop 01eb3058 00000000 f_trace 01eb305c 00000000 f_exc_type 01eb3060 00000000 f_exc_value 01eb3064 00000000 f_exc_traceback 01eb3068 01f72cf0 f_tstate 01eb306c ffffffff f_lasti 01eb3070 0000002f f_lineno 01eb3074 00000000 f_iblock 01eb3078 baadf00d f_blockstack[20] = {b_type, 01eb307c baadf00d b_handler 01eb3080 baadf00d b_level} ... 01eb3168 023bf550 f_localsplus // frame + 0x138 01eb316c 01f7d8a0 01eb3170 baadf00d 01eb3174 baadf00d Что в памяти
  • 13. PyObject * PyEval_EvalCodeEx(PyCodeObject *co, PyObject *globals, PyObject *locals, PyObject **args, int argcount, PyObject **kws, int kwcount, PyObject **defs, int defcount, PyObject *closure) { ... n = co->co_argcount; for (i = 0; i < n; i++) { x = args[i]; Py_INCREF(x); SETLOCAL(i, x); } ... https://github.com/python/cpython/blob/2.7/Python/ceval.c #define Py_INCREF(op) ( _Py_INC_REFTOTAL _Py_REF_DEBUG_COMMA ((PyObject*)(op))->ob_refcnt++) #define GETLOCAL(i) (fastlocals[i]) #define SETLOCAL(i, value) do { PyObject *tmp = GETLOCAL(i); GETLOCAL(i) = value; Py_XDECREF(tmp); } while (0)
  • 14. #define Py_XDECREF(op) do { if ((op) == NULL); else Py_DECREF(op); } while (0) #define Py_DECREF(op) do { if (_Py_DEC_REFTOTAL _Py_REF_DEBUG_COMMA --((PyObject*)(op))->ob_refcnt != 0) _Py_CHECK_REFCNT(op) else _Py_Dealloc((PyObject *)(op)); } while (0) #define _Py_Dealloc(op) ( _Py_INC_TPFREES(op) _Py_COUNT_ALLOCS_COMMA (*Py_TYPE(op)->tp_dealloc)((PyObject *)(op))) #endif /* !Py_TRACE_REFS */ https://github.com/python/cpython/blob/2.7/Include/object.h
  • 15. typedef struct _frame { PyObject_VAR_HEAD struct _frame *f_back; /* previous frame, or NULL */ PyCodeObject *f_code; /* code segment */ PyObject *f_builtins; /* builtin symbol table (PyDictObject) */ PyObject *f_globals; /* global symbol table (PyDictObject) */ PyObject *f_locals; /* local symbol table (any mapping) */ PyObject **f_valuestack; /* points after the last local */ PyObject **f_stacktop; PyObject *f_trace; /* Trace function */ PyObject *f_exc_type, *f_exc_value, *f_exc_traceback; PyThreadState *f_tstate; int f_lasti; /* Last instruction if called */ int f_lineno; /* Current line number */ int f_iblock; /* index in f_blockstack */ PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */ PyObject *f_localsplus[1]; /* locals+stack, dynamically sized */ } PyFrameObject; https://github.com/python/cpython/blob/2.7/Include/frameobject.h
  • 16. ncells = PyTuple_GET_SIZE(code->co_cellvars); nfrees = PyTuple_GET_SIZE(code->co_freevars); extras = code->co_stacksize + code->co_nlocals + ncells + nfrees; if (free_list == NULL) { f = PyObject_GC_NewVar(PyFrameObject, &PyFrame_Type, extras); ... https://github.com/python/cpython/blob/2.7/Objects/frameobject.c co_cellvars – is a tuple with the names of local variables referenced by nested functions; co_freevars – names of variables in the function, defined in an enclosing function scope; Память для f_localsplus
  • 17. Проблема найдена ret_code = types.CodeType( nargs + 1, attrs['ret_func'].func_code.co_nlocals, attrs['ret_func'].func_code.co_stacksize, attrs['ret_func'].func_code.co_flags, attrs['ret_func'].func_code.co_code, attrs['ret_func'].func_code.co_consts, attrs['ret_func'].func_code.co_names, varnames, attrs['ret_func'].func_code.co_filename, 'call', attrs['ret_func'].func_code.co_firstlineno, attrs['ret_func'].func_code.co_lnotab, (), # freevars () # cellvars )
  • 18. Попробуем применить def create_func(fun, nargs): def wrapper(): args = map(itemgetter(1), sorted(((var_name, arg) for (var_name, arg) in locals().items() if var_name != 'fun'), key=itemgetter(0))) fun(*args) varnames = tuple(str(i) for i in range(0, nargs)) ret_code = types.CodeType(nargs, wrapper.func_code.co_nlocals + nargs, # Параметры ) return types.FunctionType(ret_code, wrapper.func_globals, wrapper.func_code.co_name) def test_func(*args): print args fun = create_func(test_func, 3) fun(1, 2, 3)
  • 19. Как-то не очень Traceback (most recent call last): File "func.py", line 38, in <module> fun(1,2,3) File "func.py", line 16, in wrapper fun(*args) SystemError: Objects/cellobject.c:24: bad argument to internal function
  • 20. Заработало! def create_func(fun, nargs): def wrapper(fun): args = map(itemgetter(1), sorted(((var_name, arg) for (var_name, arg) in locals().items() if var_name != 'fun'), key=itemgetter(0))) fun(*args) varnames = tuple(['fun'] + [str(i) for i in range(0, nargs)]) ret_code = types.CodeType(nargs + 1, wrapper.func_code.co_nlocals + nargs, # Параметры ) return partial(types.FunctionType(ret_code, wrapper.func_globals, wrapper.func_code.co_name), fun) def test_func(*args): print args fun = create_func(test_func, 3) fun(1, 2, 3)
  • 21. Что-то потеряли >>> dir(wrapper.func_code) ['__class__', '__cmp__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'co_argcount', 'co_cellvars', 'co_code', 'co_consts', 'co_filename', 'co_firstlineno', 'co_flags', 'co_freevars', 'co_lnotab', 'co_name', 'co_names', 'co_nlocals', 'co_stacksize', 'co_varnames']
  • 22. Добавим параметр ret_code = types.CodeType(nargs, wrapper.func_code.co_nlocals + nargs, # Параметры wrapper.func_code.co_freevars) return types.FunctionType(ret_code, wrapper.func_globals, wrapper.func_code.co_name) >>>Traceback (most recent call last): File "func.py", line 39, in <module> fun = create_func(test_func, 3) File "func.py", line 33, in create_func return types.FunctionType(ret_code, wrapper.func_globals, wrapper.func_code.co_name) TypeError: arg 5 (closure) must be tuple
  • 23. def create_func(fun, nargs): def wrapper(): args = map(itemgetter(1), sorted(((var_name, arg) for (var_name, arg) in locals().items() if var_name != 'fun'), key=itemgetter(0))) fun(*args) varnames = tuple([str(i) for i in range(0, nargs)]) ret_code = types.CodeType(nargs, wrapper.func_code.co_nlocals + nargs, # Параметры wrapper.func_code.co_freevars) return types.FunctionType(ret_code, wrapper.func_globals, wrapper.func_code.co_name, None, wrapper.func_closure) def test_func(*args): print args fun = create_func(test_func, 3) fun(1, 2, 3) И наконец, замыкания работают!
  • 24. … почти from operator import itemgetter def wrapper(): print locals() args = map(itemgetter(1), sorted(((var_name, arg) for (var_name, arg) in locals().items() if var_name != 'fun'), key=itemgetter(0))) print locals() fun(*args) {'1': 2, '0': 1, '2': 3, 'fun': <function test_func at 0x10cd62398>} {'1': 2, '0': [1, 2, 3], '2': 3, 'fun': <function test_func at 0x10cd62398>}
  • 25. Спасибо за внимание! Вопросы? SPb Python Interest Group https://telegram.me/spbpython