diff options
author | da-woods <dw-git@d-woods.co.uk> | 2021-09-24 11:05:52 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-09-24 12:05:52 +0200 |
commit | 740305526a702f08e462f20a8ba6f9013126f330 (patch) | |
tree | 4917f2b5241d679ebdb59b3b9cb7ac6a422cdfe4 | |
parent | e2a23fe11063ee13c886e89007207a07a397e016 (diff) | |
download | cython-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.c | 12 | ||||
-rw-r--r-- | tests/run/fused_bound_functions.py | 54 |
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 |