Commit d7d82683 authored by Stefan Behnel's avatar Stefan Behnel

implement binding Python methods in extension types

enables line tracing etc.
parent 46cd02cc
......@@ -1973,7 +1973,7 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
"};")
def generate_method_table(self, env, code):
if env.is_c_class_scope and not env.pyfunc_entries:
if env.is_c_class_scope and (not env.pyfunc_entries or env.directives['binding']):
return
code.putln("")
code.putln(
......
......@@ -3039,10 +3039,12 @@ class DefNode(FuncDefNode):
return False
if self.no_assignment_synthesis:
return False
if self.entry.is_special:
return False
# Should enable for module level as well, that will require more testing...
if self.entry.is_anonymous:
return True
if env.is_module_scope:
if env.is_module_scope or env.is_c_class_scope:
if code is None:
return env.directives['binding']
else:
......
......@@ -471,7 +471,7 @@ class MethodTableSlot(SlotDescriptor):
# Slot descriptor for the method table.
def slot_code(self, scope):
if scope.pyfunc_entries:
if scope.pyfunc_entries and not scope.directives['binding']:
return scope.method_table_cname
else:
return "0"
......
......@@ -571,13 +571,10 @@ __Pyx_CyFunction_repr(__pyx_CyFunctionObject *op)
#endif
}
#if CYTHON_COMPILING_IN_PYPY
// originally copied from PyCFunction_Call() in CPython's Objects/methodobject.c
// PyPy does not have this function
static PyObject * __Pyx_CyFunction_Call(PyObject *func, PyObject *arg, PyObject *kw) {
static PyObject * __Pyx_CyFunction_CallMethod(PyObject *func, PyObject *self, PyObject *arg, PyObject *kw) {
// originally copied from PyCFunction_Call() in CPython's Objects/methodobject.c
PyCFunctionObject* f = (PyCFunctionObject*)func;
PyCFunction meth = f->m_ml->ml_meth;
PyObject *self = f->m_self;
Py_ssize_t size;
switch (f->m_ml->ml_flags & (METH_VARARGS | METH_KEYWORDS | METH_NOARGS | METH_O)) {
......@@ -625,11 +622,38 @@ static PyObject * __Pyx_CyFunction_Call(PyObject *func, PyObject *arg, PyObject
f->m_ml->ml_name);
return NULL;
}
#else
static PyObject * __Pyx_CyFunction_Call(PyObject *func, PyObject *arg, PyObject *kw) {
return PyCFunction_Call(func, arg, kw);
static CYTHON_INLINE PyObject *__Pyx_CyFunction_Call(PyObject *func, PyObject *arg, PyObject *kw) {
return __Pyx_CyFunction_CallMethod(func, ((PyCFunctionObject*)func)->m_self, arg, kw);
}
static PyObject *__Pyx_CyFunction_CallAsMethod(PyObject *func, PyObject *args, PyObject *kw) {
PyObject *result;
__pyx_CyFunctionObject *cyfunc = (__pyx_CyFunctionObject *) func;
if (cyfunc->flags & __Pyx_CYFUNCTION_CCLASS) {
Py_ssize_t argc;
PyObject *new_args;
PyObject *self;
argc = PyTuple_GET_SIZE(args);
new_args = PyTuple_GetSlice(args, 1, argc);
if (unlikely(!new_args))
return NULL;
self = PyTuple_GetItem(args, 0);
if (unlikely(!self)) {
Py_DECREF(new_args);
return NULL;
}
result = __Pyx_CyFunction_CallMethod(func, self, new_args, kw);
Py_DECREF(new_args);
} else {
result = __Pyx_CyFunction_Call(func, args, kw);
}
return result;
}
#endif
static PyTypeObject __pyx_CyFunctionType_type = {
PyVarObject_HEAD_INIT(0, 0)
......@@ -650,7 +674,7 @@ static PyTypeObject __pyx_CyFunctionType_type = {
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash*/
__Pyx_CyFunction_Call, /*tp_call*/
__Pyx_CyFunction_CallAsMethod, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
......@@ -694,10 +718,6 @@ static PyTypeObject __pyx_CyFunctionType_type = {
static int __pyx_CyFunction_init(void) {
#if !CYTHON_COMPILING_IN_PYPY
// avoid a useless level of call indirection
__pyx_CyFunctionType_type.tp_call = PyCFunction_Call;
#endif
__pyx_CyFunctionType = __Pyx_FetchCommonType(&__pyx_CyFunctionType_type);
if (__pyx_CyFunctionType == NULL) {
return -1;
......@@ -968,38 +988,14 @@ static PyObject *
__pyx_FusedFunction_callfunction(PyObject *func, PyObject *args, PyObject *kw)
{
__pyx_CyFunctionObject *cyfunc = (__pyx_CyFunctionObject *) func;
PyObject *result;
int static_specialized = (cyfunc->flags & __Pyx_CYFUNCTION_STATICMETHOD &&
!((__pyx_FusedFunctionObject *) func)->__signatures__);
if (cyfunc->flags & __Pyx_CYFUNCTION_CCLASS && !static_specialized) {
Py_ssize_t argc;
PyObject *new_args;
PyObject *self;
PyObject *m_self;
argc = PyTuple_GET_SIZE(args);
new_args = PyTuple_GetSlice(args, 1, argc);
if (!new_args)
return NULL;
self = PyTuple_GetItem(args, 0);
if (!self)
return NULL;
m_self = cyfunc->func.m_self;
cyfunc->func.m_self = self;
result = __Pyx_CyFunction_Call(func, new_args, kw);
cyfunc->func.m_self = m_self;
Py_DECREF(new_args);
return __Pyx_CyFunction_CallAsMethod(func, args, kw);
} else {
result = __Pyx_CyFunction_Call(func, args, kw);
return __Pyx_CyFunction_Call(func, args, kw);
}
return result;
}
// Note: the 'self' from method binding is passed in in the args tuple,
......
......@@ -21,8 +21,6 @@ setup(
######## test_profile.py ###########
from collatz import collatz
try:
import line_profiler
except ImportError:
......@@ -30,20 +28,45 @@ except ImportError:
import sys
sys.exit(0)
profile = line_profiler.LineProfiler(collatz)
profile.runcall(collatz, 19)
profile.print_stats()
stats = profile.get_stats()
assert len(stats.timings) > 0, "No profile stats."
for key, timings in stats.timings.items():
if key[-1] == 'collatz':
assert len(timings) > 0
break
else:
raise ValueError("No stats for collatz.")
def assert_stats(profile, name):
profile.print_stats()
stats = profile.get_stats()
assert len(stats.timings) > 0, "No profile stats."
for key, timings in stats.timings.items():
if key[-1] == name:
assert len(timings) > 0
break
else:
raise ValueError("No stats for %s." % name)
from collatz import collatz
func = collatz
profile = line_profiler.LineProfiler(func)
profile.runcall(func, 19)
assert_stats(profile, func.__name__)
from collatz import PyClass
obj = PyClass()
func = obj.py_pymethod
profile = line_profiler.LineProfiler(func)
profile.runcall(func)
assert_stats(profile, func.__name__)
from collatz import CClass
obj = CClass()
func = obj.c_pymethod
print(func)
print(type(func))
print(func.__func__)
profile = line_profiler.LineProfiler(func)
profile.runcall(func)
assert_stats(profile, func.__name__)
######## collatz.pyx ###########
# cython: binding=True
def collatz(n):
while n > 1:
......@@ -51,3 +74,24 @@ def collatz(n):
n //= 2
else:
n = 3*n+1
class PyClass(object):
def py_pymethod(self):
x = 1
for i in range(10):
a = x + 2
return a * 3
cdef class CClass:
def c_pymethod(self, c=2):
for i in range(10):
a = c + 1
y = self.cmethod(c + a)
return y * 4
cdef cmethod(self, s):
for i in range(10):
p = s + 3
return p * 5
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment