Commit 395b9584 authored by Stefan Behnel's avatar Stefan Behnel

Merge branch '_binding_methods'

parents ceafa531 5f0eb2c6
......@@ -46,6 +46,9 @@ Bugs fixed
* Some function signatures in ``libc.math`` and ``numpy.pxd`` were incorrect.
Patch by Michael Seifert.
* Misplaced usages of the module-level ``linetrace`` and ``profile`` directives
were silently ignored.
Other changes
-------------
......
......@@ -1877,11 +1877,13 @@ class NameNode(AtomicExprNode):
def analyse_target_types(self, env):
self.analyse_entry(env, is_target=True)
if self.entry.is_cfunction and self.entry.as_variable:
if self.entry.is_overridable or not self.is_lvalue() and self.entry.fused_cfunction:
entry = self.entry
if entry.is_cfunction and entry.as_variable:
# FIXME: unify "is_overridable" flags below
if (entry.is_overridable or entry.type.is_overridable) or not self.is_lvalue() and entry.fused_cfunction:
# We need this for assigning to cpdef names and for the fused 'def' TreeFragment
self.entry = self.entry.as_variable
self.type = self.entry.type
entry = self.entry = entry.as_variable
self.type = entry.type
if self.type.is_const:
error(self.pos, "Assignment to const '%s'" % self.name)
......@@ -1890,10 +1892,10 @@ class NameNode(AtomicExprNode):
if not self.is_lvalue():
error(self.pos, "Assignment to non-lvalue '%s'" % self.name)
self.type = PyrexTypes.error_type
self.entry.used = 1
if self.entry.type.is_buffer:
entry.used = 1
if entry.type.is_buffer:
from . import Buffer
Buffer.used_buffer_aux_vars(self.entry)
Buffer.used_buffer_aux_vars(entry)
return self
def analyse_rvalue_entry(self, env):
......@@ -5583,7 +5585,7 @@ class PyMethodCallNode(SimpleCallNode):
len(args)+1,
self_arg,
', '.join(arg.py_result() for arg in args)))
code.putln("%s = __Pyx_PyFunction_FastCall(%s, %s+1-%s, %d+%s, NULL); %s" % (
code.putln("%s = __Pyx_PyFunction_FastCall(%s, %s+1-%s, %d+%s); %s" % (
self.result(),
function,
Naming.quick_temp_cname,
......@@ -8730,8 +8732,16 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin):
default_args = []
default_kwargs = []
annotations = []
# For global cpdef functions and def/cpdef methods in cdef classes, we must use global constants
# for default arguments to avoid the dependency on the CyFunction object as 'self' argument
# in the underlying C function. Basically, cpdef functions/methods are static C functions,
# so their optional arguments must be static, too.
# TODO: change CyFunction implementation to pass both function object and owning object for method calls
must_use_constants = env.is_c_class_scope or (self.def_node.is_wrapper and env.is_module_scope)
for arg in self.def_node.args:
if arg.default:
if arg.default and not must_use_constants:
if not arg.default.is_literal:
arg.is_dynamic = True
if arg.type.is_pyobject:
......@@ -8820,8 +8830,10 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin):
self.pos, args=[defaults_tuple, defaults_kwdict])),
decorators=None,
name=StringEncoding.EncodedString("__defaults__"))
defaults_getter.analyse_declarations(env)
defaults_getter = defaults_getter.analyse_expressions(env)
# defaults getter must never live in class scopes, it's always a module function
module_scope = env.global_scope()
defaults_getter.analyse_declarations(module_scope)
defaults_getter = defaults_getter.analyse_expressions(module_scope)
defaults_getter.body = defaults_getter.body.analyse_expressions(
defaults_getter.local_scope)
defaults_getter.py_wrapper_required = False
......@@ -8891,7 +8903,7 @@ class PyCFunctionNode(ExprNode, ModuleNameMixin):
elif def_node.is_classmethod:
flags.append('__Pyx_CYFUNCTION_CLASSMETHOD')
if def_node.local_scope.parent_scope.is_c_class_scope:
if def_node.local_scope.parent_scope.is_c_class_scope and not def_node.entry.is_anonymous:
flags.append('__Pyx_CYFUNCTION_CCLASS')
if flags:
......
......@@ -1975,12 +1975,13 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
def generate_method_table(self, env, code):
if env.is_c_class_scope and not env.pyfunc_entries:
return
binding = env.directives['binding']
code.putln("")
code.putln(
"static PyMethodDef %s[] = {" % (
env.method_table_cname))
for entry in env.pyfunc_entries:
if not entry.fused_cfunction:
if not entry.fused_cfunction and not (binding and entry.is_overridable):
code.put_pymethoddef(entry, ",")
code.putln(
"{0, 0, 0, 0}")
......
......@@ -197,6 +197,7 @@ class Node(object):
is_nonecheck = 0
is_literal = 0
is_terminator = 0
is_wrapper = False # is a DefNode wrapper for a C function
temps = None
# All descendants should set child_attrs to a list of the attributes
......@@ -1879,7 +1880,7 @@ class FuncDefNode(StatNode, BlockNode):
if profile or linetrace:
# this looks a bit late, but if we don't get here due to a
# fatal error before hand, it's not really worth tracing
if isinstance(self, DefNode) and self.is_wrapper:
if self.is_wrapper:
trace_name = self.entry.name + " (wrapper)"
else:
trace_name = self.entry.name
......@@ -2243,6 +2244,11 @@ class CFuncDefNode(FuncDefNode):
def unqualified_name(self):
return self.entry.name
@property
def code_object(self):
# share the CodeObject with the cpdef wrapper (if available)
return self.py_func.code_object if self.py_func else None
def analyse_declarations(self, env):
self.is_c_class_method = env.is_c_class_scope
if self.directive_locals is None:
......@@ -2360,6 +2366,7 @@ class CFuncDefNode(FuncDefNode):
is_wrapper=1)
self.py_func.is_module_scope = env.is_module_scope
self.py_func.analyse_declarations(env)
self.py_func.entry.is_overridable = True
self.py_func_stat = StatListNode(self.pos, stats=[self.py_func])
self.py_func.type = PyrexTypes.py_object_type
self.entry.as_variable = self.py_func.entry
......@@ -2436,7 +2443,10 @@ class CFuncDefNode(FuncDefNode):
def analyse_expressions(self, env):
self.local_scope.directives = env.directives
if self.py_func is not None:
if self.py_func_stat is not None:
# this will also analyse the default values and the function name assignment
self.py_func_stat = self.py_func_stat.analyse_expressions(env)
elif self.py_func is not None:
# this will also analyse the default values
self.py_func = self.py_func.analyse_expressions(env)
else:
......@@ -3022,16 +3032,17 @@ class DefNode(FuncDefNode):
def needs_assignment_synthesis(self, env, code=None):
if self.is_staticmethod:
return True
if self.is_wrapper or self.specialized_cpdefs or self.entry.is_fused_specialized:
if self.specialized_cpdefs or self.entry.is_fused_specialized:
return False
if self.no_assignment_synthesis:
return False
# Should enable for module level as well, that will require more testing...
if self.entry.is_special:
return False
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']
return self.local_scope.directives['binding']
else:
return code.globalstate.directives['binding']
return env.is_py_class_scope or env.is_closure_scope
......@@ -3044,7 +3055,8 @@ class DefNode(FuncDefNode):
def generate_function_definitions(self, env, code):
if self.defaults_getter:
self.defaults_getter.generate_function_definitions(env, code)
# defaults getter must never live in class scopes, it's always a module function
self.defaults_getter.generate_function_definitions(env.global_scope(), code)
# Before closure cnames are mangled
if self.py_wrapper_required:
......
......@@ -1697,6 +1697,8 @@ if VALUE is not None:
def visit_DefNode(self, node):
node = self.visit_FuncDefNode(node)
env = self.current_env()
if isinstance(node, Nodes.DefNode) and node.is_wrapper:
env = env.parent_scope
if (not isinstance(node, Nodes.DefNode) or
node.fused_py_func or node.is_generator_body or
not node.needs_assignment_synthesis(env)):
......@@ -1949,13 +1951,28 @@ class CalculateQualifiedNamesTransform(EnvTransform):
return node
def visit_PyCFunctionNode(self, node):
orig_qualified_name = self.qualified_name[:]
if node.def_node.is_wrapper and self.qualified_name and self.qualified_name[-1] == '<locals>':
self.qualified_name.pop()
self._set_qualname(node)
else:
self._set_qualname(node, node.def_node.name)
self.visitchildren(node)
self.qualified_name = orig_qualified_name
return node
def visit_DefNode(self, node):
if node.is_wrapper and self.qualified_name:
assert self.qualified_name[-1] == '<locals>', self.qualified_name
orig_qualified_name = self.qualified_name[:]
self.qualified_name.pop()
self._set_qualname(node)
self._super_visit_FuncDefNode(node)
self.qualified_name = orig_qualified_name
else:
self._set_qualname(node, node.name)
return self.visit_FuncDefNode(node)
self.visit_FuncDefNode(node)
return node
def visit_FuncDefNode(self, node):
orig_qualified_name = self.qualified_name[:]
......
......@@ -99,6 +99,12 @@ class Signature(object):
self.exception_check = ret_format != 'r' and self.error_value is not None
self.is_staticmethod = False
def __repr__(self):
return '<Signature[%s(%s%s)]>' % (
self.ret_format,
', '.join(self.fixed_arg_format),
'*' if self.has_generic_args else '')
def num_fixed_args(self):
return len(self.fixed_arg_format)
......
......@@ -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,
......
......@@ -1273,7 +1273,7 @@ static PyObject* __Pyx_PyObject_CallMethod1(PyObject* obj, PyObject* method_name
#if CYTHON_FAST_PYCALL
if (PyFunction_Check(function)) {
PyObject *args[2] = {self, arg};
result = __Pyx_PyFunction_FastCall(function, args, 2, NULL);
result = __Pyx_PyFunction_FastCall(function, args, 2);
goto done;
}
#endif
......@@ -1321,7 +1321,7 @@ static PyObject* __Pyx_PyObject_CallMethod2(PyObject* obj, PyObject* method_name
#if CYTHON_FAST_PYCALL
if (PyFunction_Check(function)) {
PyObject *args[3] = {self, arg1, arg2};
result = __Pyx_PyFunction_FastCall(function, args, 3, NULL);
result = __Pyx_PyFunction_FastCall(function, args, 3);
goto done;
}
#endif
......@@ -1341,7 +1341,7 @@ static PyObject* __Pyx_PyObject_CallMethod2(PyObject* obj, PyObject* method_name
#if CYTHON_FAST_PYCALL
if (PyFunction_Check(method)) {
PyObject *args[2] = {arg1, arg2};
result = __Pyx_PyFunction_FastCall(method, args, 2, NULL);
result = __Pyx_PyFunction_FastCall(method, args, 2);
goto done;
} else
#endif
......@@ -1433,11 +1433,14 @@ static CYTHON_INLINE PyObject* __Pyx_PyObject_CallMethO(PyObject *func, PyObject
/////////////// PyFunctionFastCall.proto ///////////////
#if CYTHON_FAST_PYCALL
#define __Pyx_PyFunction_FastCall(func, args, nargs) \
__Pyx_PyFunction_FastCallDict((func), (args), (nargs), NULL)
// let's assume that the non-public C-API function might still change during the 3.6 beta phase
#if 1 || PY_VERSION_HEX < 0x030600B1
static PyObject *__Pyx_PyFunction_FastCall(PyObject *func, PyObject **args, int nargs, PyObject *kwargs);
static PyObject *__Pyx_PyFunction_FastCallDict(PyObject *func, PyObject **args, int nargs, PyObject *kwargs);
#else
#define __Pyx_PyFunction_FastCall(func, args, nargs, kwargs) _PyFunction_FastCall(func, args, nargs, kwargs)
#define __Pyx_PyFunction_FastCallDict(func, args, nargs, kwargs) _PyFunction_FastCallDict(func, args, nargs, kwargs)
#endif
#endif
......@@ -1447,8 +1450,8 @@ static PyObject *__Pyx_PyFunction_FastCall(PyObject *func, PyObject **args, int
#if CYTHON_FAST_PYCALL
#include "frameobject.h"
static PyObject* __Pyx_PyFunction_FastCallNoKw(PyObject **args, Py_ssize_t na,
PyCodeObject *co, PyObject *globals) {
static PyObject* __Pyx_PyFunction_FastCallNoKw(PyCodeObject *co, PyObject **args, Py_ssize_t na,
PyObject *globals) {
PyFrameObject *f;
PyThreadState *tstate = PyThreadState_GET();
PyObject **fastlocals;
......@@ -1483,7 +1486,7 @@ static PyObject* __Pyx_PyFunction_FastCallNoKw(PyObject **args, Py_ssize_t na,
#if 1 || PY_VERSION_HEX < 0x030600B1
static PyObject *__Pyx_PyFunction_FastCall(PyObject *func, PyObject **args, int nargs, CYTHON_UNUSED PyObject *kwargs) {
static PyObject *__Pyx_PyFunction_FastCallDict(PyObject *func, PyObject **args, int nargs, PyObject *kwargs) {
PyCodeObject *co = (PyCodeObject *)PyFunction_GET_CODE(func);
PyObject *globals = PyFunction_GET_GLOBALS(func);
PyObject *argdefs = PyFunction_GET_DEFAULTS(func);
......@@ -1494,18 +1497,60 @@ static PyObject *__Pyx_PyFunction_FastCall(PyObject *func, PyObject **args, int
//PyObject *name, *qualname;
//#endif
#endif
PyObject *kwtuple, **k;
PyObject **d;
int nd;
Py_ssize_t nk;
PyObject *result;
assert(kwargs == NULL || PyDict_Check(kwargs));
nk = kwargs ? PyDict_Size(kwargs) : 0;
/* CPython issue #27128: support for keywords will come later */
assert(kwargs == NULL);
if (Py_EnterRecursiveCall(" while calling a Python object")) {
return NULL;
}
if (argdefs == NULL && co->co_argcount == nargs &&
if (
#if PY_MAJOR_VERSION >= 3
co->co_kwonlyargcount == 0 &&
#endif
likely(kwargs == NULL || nk == 0) &&
co->co_flags == (CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE)) {
return __Pyx_PyFunction_FastCallNoKw(args, nargs, co, globals);
/* Fast paths */
if (argdefs == NULL && co->co_argcount == nargs) {
result = __Pyx_PyFunction_FastCallNoKw(co, args, nargs, globals);
goto done;
}
else if (nargs == 0 && argdefs != NULL
&& co->co_argcount == Py_SIZE(argdefs)) {
/* function called with no arguments, but all parameters have
a default value: use default values as arguments .*/
args = &PyTuple_GET_ITEM(argdefs, 0);
result =__Pyx_PyFunction_FastCallNoKw(co, args, Py_SIZE(argdefs), globals);
goto done;
}
}
if (kwargs != NULL) {
Py_ssize_t pos, i;
kwtuple = PyTuple_New(2 * nk);
if (kwtuple == NULL) {
result = NULL;
goto done;
}
k = &PyTuple_GET_ITEM(kwtuple, 0);
pos = i = 0;
while (PyDict_Next(kwargs, &pos, &k[i], &k[i+1])) {
Py_INCREF(k[i]);
Py_INCREF(k[i+1]);
i += 2;
}
nk = i / 2;
}
else {
kwtuple = NULL;
k = NULL;
}
closure = PyFunction_GET_CLOSURE(func);
......@@ -1534,16 +1579,21 @@ static PyObject *__Pyx_PyFunction_FastCall(PyObject *func, PyObject **args, int
// closure, name, qualname);
//#elif PY_MAJOR_VERSION >= 3
#if PY_MAJOR_VERSION >= 3
return PyEval_EvalCodeEx((PyObject*)co, globals, (PyObject *)NULL,
result = PyEval_EvalCodeEx((PyObject*)co, globals, (PyObject *)NULL,
args, nargs,
NULL, 0,
k, (int)nk,
d, nd, kwdefs, closure);
#else
return PyEval_EvalCodeEx(co, globals, (PyObject *)NULL,
result = PyEval_EvalCodeEx(co, globals, (PyObject *)NULL,
args, nargs,
NULL, 0,
k, (int)nk,
d, nd, closure);
#endif
Py_XDECREF(kwtuple);
done:
Py_LeaveRecursiveCall();
return result;
}
#endif // CPython < 3.6
#endif // CYTHON_FAST_PYCALL
......@@ -1573,7 +1623,7 @@ static PyObject* __Pyx__PyObject_CallOneArg(PyObject *func, PyObject *arg) {
static CYTHON_INLINE PyObject* __Pyx_PyObject_CallOneArg(PyObject *func, PyObject *arg) {
#if CYTHON_FAST_PYCALL
if (PyFunction_Check(func)) {
return __Pyx_PyFunction_FastCall(func, &arg, 1, NULL);
return __Pyx_PyFunction_FastCall(func, &arg, 1);
}
#endif
#ifdef __Pyx_CyFunction_USED
......@@ -1620,7 +1670,7 @@ static CYTHON_INLINE PyObject* __Pyx_PyObject_CallNoArg(PyObject *func); /*proto
static CYTHON_INLINE PyObject* __Pyx_PyObject_CallNoArg(PyObject *func) {
#if CYTHON_FAST_PYCALL
if (PyFunction_Check(func)) {
return __Pyx_PyFunction_FastCall(func, NULL, 0, NULL);
return __Pyx_PyFunction_FastCall(func, NULL, 0);
}
#endif
#ifdef __Pyx_CyFunction_USED
......@@ -1667,7 +1717,7 @@ static PyObject* __Pyx_PyObject_CallMatrixMethod(PyObject* method, PyObject* arg
#if CYTHON_FAST_PYCALL
if (PyFunction_Check(function)) {
PyObject *args[2] = {self, arg};
result = __Pyx_PyFunction_FastCall(function, args, 2, NULL);
result = __Pyx_PyFunction_FastCall(function, args, 2);
goto done;
}
#endif
......
# mode: run
# tag: cyfunction
# cython: binding=True
cimport cython
class PyClass(object):
a = 2
class PyClass99(object):
a = 99
def pymethod(self, x, y=1, z=PyClass):
"""
>>> obj = PyClass99()
>>> obj.pymethod(0)
(0, 1, 2)
"""
return x, y, z.a
cdef class CyClass:
cpdef cpmethod(self, x, y=1, z=PyClass):
"""
>>> obj = CyClass()
>>> obj.cpmethod(0)
(0, 1, 2)
>>> obj.cpmethod(0, 3)
(0, 3, 2)
>>> obj.cpmethod(0, 3, PyClass)
(0, 3, 2)
>>> obj.cpmethod(0, 3, 5)
Traceback (most recent call last):
AttributeError: 'int' object has no attribute 'a'
"""
return x, y, z.a
y_value = 3
p_class = PyClass
cpdef cpmethod2(self, x, y=y_value, z=p_class):
"""
>>> obj = CyClass()
>>> obj.cpmethod2(0)
(0, 3, 2)
"""
return x, y, z.a
def pymethod(self, x, y=y_value, z=p_class):
"""
>>> obj = CyClass()
>>> obj.pymethod(0)
(0, 3, 2)
"""
return x, y, z.a
# change values to check that defaults above stay unmodified
y_value = 98
p_class = PyClass99
cpdef func(x, y=1, z=PyClass):
"""
>>> func(0)
(0, 1, 2)
>>> func(0, 3)
(0, 3, 2)
>>> func(0, 3, PyClass)
(0, 3, 2)
>>> func(0, 3, 5)
Traceback (most recent call last):
AttributeError: 'int' object has no attribute 'a'
"""
return x, y, z.a
@cython.ccall
def pyfunc(x, y=1, z=PyClass):
"""
>>> pyfunc(0)
(0, 1, 2)
>>> pyfunc(0, 3)
(0, 3, 2)
>>> pyfunc(0, 3, PyClass)
(0, 3, 2)
>>> pyfunc(0, 3, 5)
Traceback (most recent call last):
AttributeError: 'int' object has no attribute 'a'
"""
return x, y, z.a
cpdef func(x, y=*, z=*)
# mode: run
# tag: cyfunction
# cython: binding=True
import cython
class PyClass(object):
a = 2
class PyClass99(object):
a = 99
def pymethod(self, x, y=1, z=PyClass):
"""
>>> obj = PyClass99()
>>> obj.pymethod(0)
(0, 1, 2)
"""
return x, y, z.a
def func(x, y=1, z=PyClass):
"""
>>> func(0)
(0, 1, 2)
>>> func(0, 3)
(0, 3, 2)
>>> func(0, 3, PyClass)
(0, 3, 2)
>>> func(0, 3, 5)
Traceback (most recent call last):
AttributeError: 'int' object has no attribute 'a'
"""
return x, y, z.a
@cython.ccall
def pyfunc(x, y=1, z=PyClass):
"""
>>> pyfunc(0)
(0, 1, 2)
>>> pyfunc(0, 3)
(0, 3, 2)
>>> pyfunc(0, 3, PyClass)
(0, 3, 2)
>>> pyfunc(0, 3, 5)
Traceback (most recent call last):
AttributeError: 'int' object has no attribute 'a'
"""
return x, y, z.a
......@@ -7,10 +7,6 @@ from distutils.extension import Extension
from distutils.core import setup
from Cython.Build import cythonize
from Cython.Compiler.Options import _directive_defaults
_directive_defaults['linetrace'] = True
_directive_defaults['binding'] = True
extensions = [
Extension("collatz", ["collatz.pyx"], define_macros=[('CYTHON_TRACE', '1')])
]
......@@ -21,8 +17,6 @@ setup(
######## test_profile.py ###########
from collatz import collatz
try:
import line_profiler
except ImportError:
......@@ -30,24 +24,98 @@ 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':
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 collatz.")
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 cp_collatz
func = cp_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
profile = line_profiler.LineProfiler(func)
profile.runcall(func)
assert_stats(profile, func.__name__)
func = obj.cp_pymethod
profile = line_profiler.LineProfiler(func)
profile.runcall(func, 19)
assert_stats(profile, func.__name__)
######## collatz.pyx ###########
# cython: linetrace=True
cimport cython
@cython.binding(True)
def collatz(n):
while n > 1:
if n % 2 == 0:
n //= 2
else:
n = 3*n+1
@cython.binding(True)
cpdef cp_collatz(n):
while n > 1:
if n % 2 == 0:
n //= 2
else:
n = 3*n+1
@cython.binding(True)
class PyClass(object):
def py_pymethod(self):
x = 1
for i in range(10):
a = x + 2
return a * 3
@cython.binding(True)
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
cpdef cp_pymethod(self, r):
for i in range(10):
a = r + 1
z = self.c_pymethod(a) + self.cmethod(r)
return z * 2
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