summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorda-woods <dw-git@d-woods.co.uk>2021-09-24 11:05:52 +0100
committerGitHub <noreply@github.com>2021-09-24 12:05:52 +0200
commit740305526a702f08e462f20a8ba6f9013126f330 (patch)
tree4917f2b5241d679ebdb59b3b9cb7ac6a422cdfe4
parente2a23fe11063ee13c886e89007207a07a397e016 (diff)
downloadcython-740305526a702f08e462f20a8ba6f9013126f330.tar.gz
Avoid unnecessary binding of fused functions on class lookup (GH-4370)
Among other things this makes it pickleable by ensuring that it's the same object each time.
-rw-r--r--Cython/Utility/CythonFunction.c12
-rw-r--r--tests/run/fused_bound_functions.py54
2 files changed, 63 insertions, 3 deletions
diff --git a/Cython/Utility/CythonFunction.c b/Cython/Utility/CythonFunction.c
index 4e3847529..65cce4f3e 100644
--- a/Cython/Utility/CythonFunction.c
+++ b/Cython/Utility/CythonFunction.c
@@ -1227,6 +1227,15 @@ __pyx_FusedFunction_descr_get(PyObject *self, PyObject *obj, PyObject *type)
if (obj == Py_None)
obj = NULL;
+ if (func->func.flags & __Pyx_CYFUNCTION_CLASSMETHOD)
+ obj = type;
+
+ if (obj == NULL) {
+ // We aren't actually binding to anything, save the effort of rebinding
+ Py_INCREF(self);
+ return self;
+ }
+
meth = (__pyx_FusedFunctionObject *) __pyx_FusedFunction_New(
((PyCFunctionObject *) func)->m_ml,
((__pyx_CyFunctionObject *) func)->flags,
@@ -1267,9 +1276,6 @@ __pyx_FusedFunction_descr_get(PyObject *self, PyObject *obj, PyObject *type)
Py_XINCREF(func->func.defaults_tuple);
meth->func.defaults_tuple = func->func.defaults_tuple;
- if (func->func.flags & __Pyx_CYFUNCTION_CLASSMETHOD)
- obj = type;
-
Py_XINCREF(obj);
meth->self = obj;
diff --git a/tests/run/fused_bound_functions.py b/tests/run/fused_bound_functions.py
index 650bc51ff..4ca51890f 100644
--- a/tests/run/fused_bound_functions.py
+++ b/tests/run/fused_bound_functions.py
@@ -36,6 +36,10 @@ def regular_func(x):
def regular_func_0():
return
+@classmethod
+def fused_classmethod_free(cls, x: IntOrFloat):
+ return (cls.__name__, type(x).__name__)
+
@cython.cclass
class Cdef:
__doc__ = """
@@ -62,6 +66,20 @@ class Cdef:
>>> c.regular_func_0() # doctest: +ELLIPSIS
Traceback (most recent call last):
TypeError: regular_func_0() takes ... arguments ...1... given...
+
+ # Looking up a class attribute doesn't go through all of __get__
+ >>> Cdef.fused_in_class is Cdef.fused_in_class
+ True
+
+ # looking up a classmethod does go through __get__ though
+ >>> Cdef.fused_classmethod is Cdef.fused_classmethod
+ False
+ >>> Cdef.fused_classmethod_free is Cdef.fused_classmethod_free
+ False
+ >>> Cdef.fused_classmethod(1)
+ ('Cdef', 'int')
+ >>> Cdef.fused_classmethod_free(1)
+ ('Cdef', 'int')
""".format(typeofCdef = 'Python object' if cython.compiled else 'Cdef')
if cython.compiled:
@@ -78,18 +96,29 @@ class Cdef:
>>> c.fused_func_0['float']() # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
TypeError: (Exception looks quite different in Python2 and 3 so no way to match both)
+
+ >>> Cdef.fused_classmethod['float'] is Cdef.fused_classmethod['float']
+ False
+ >>> Cdef.fused_classmethod_free['float'] is Cdef.fused_classmethod_free['float']
+ False
"""
fused_func = fused_func
fused_func_0 = fused_func_0
regular_func = regular_func
regular_func_0 = regular_func_0
+ fused_classmethod_free = fused_classmethod_free
+
def fused_in_class(self, x: MyFusedClass):
return (type(x).__name__, cython.typeof(x))
def regular_in_class(self):
return type(self).__name__
+ @classmethod
+ def fused_classmethod(cls, x: IntOrFloat):
+ return (cls.__name__, type(x).__name__)
+
class Regular(object):
__doc__ = """
>>> c = Regular()
@@ -111,6 +140,20 @@ class Regular(object):
>>> c.regular_func_0() # doctest: +ELLIPSIS
Traceback (most recent call last):
TypeError: regular_func_0() takes ... arguments ...1... given...
+
+ # Looking up a class attribute doesn't go through all of __get__
+ >>> Regular.fused_func is Regular.fused_func
+ True
+
+ # looking up a classmethod does go __get__ though
+ >>> Regular.fused_classmethod is Regular.fused_classmethod
+ False
+ >>> Regular.fused_classmethod_free is Regular.fused_classmethod_free
+ False
+ >>> Regular.fused_classmethod(1)
+ ('Regular', 'int')
+ >>> Regular.fused_classmethod_free(1)
+ ('Regular', 'int')
""".format(typeofRegular = "Python object" if cython.compiled else 'Regular')
if cython.compiled:
__doc__ += """
@@ -125,6 +168,11 @@ class Regular(object):
TypeError: (Exception looks quite different in Python2 and 3 so no way to match both)
>>> Regular.fused_func_0['float']()
('float', 'float')
+
+ >>> Regular.fused_classmethod['float'] is Regular.fused_classmethod['float']
+ False
+ >>> Regular.fused_classmethod_free['float'] is Regular.fused_classmethod_free['float']
+ False
"""
fused_func = fused_func
@@ -132,6 +180,12 @@ class Regular(object):
regular_func = regular_func
regular_func_0 = regular_func_0
+ fused_classmethod_free = fused_classmethod_free
+
+ @classmethod
+ def fused_classmethod(cls, x: IntOrFloat):
+ return (cls.__name__, type(x).__name__)
+
import sys
if sys.version_info[0] > 2:
# extra Py3 only tests - shows that functions added to a class can be called