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)
● Число передаваемых аргументов я узнаю в рантайме
● Объект функции в это время уже создан
Что же делать?
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
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)