diff options
author | mattip <matti.picus@gmail.com> | 2018-09-25 13:19:41 +0300 |
---|---|---|
committer | mattip <matti.picus@gmail.com> | 2018-09-25 14:43:41 +0300 |
commit | 4a8e481a419d034e1ad47d2a3c9fb244e91d19a0 (patch) | |
tree | e930352f5671dd578103eafb3e0c512711a23b25 | |
parent | 9f2869067b2cf8e86aa0c52fad90a477fe7fae87 (diff) | |
download | cython-4a8e481a419d034e1ad47d2a3c9fb244e91d19a0.tar.gz |
DOC, TST: test and document external extension type attribute aliasing
-rw-r--r-- | docs/src/userguide/extension_types.rst | 66 | ||||
-rw-r--r-- | docs/src/userguide/external_C_code.rst | 1 | ||||
-rw-r--r-- | tests/run/ext_attr_getter.srctree | 109 |
3 files changed, 176 insertions, 0 deletions
diff --git a/docs/src/userguide/extension_types.rst b/docs/src/userguide/extension_types.rst index 0f0282ea2..340fac6fc 100644 --- a/docs/src/userguide/extension_types.rst +++ b/docs/src/userguide/extension_types.rst @@ -771,6 +771,72 @@ For public extension types, the object and type clauses are both required, because Cython must be able to generate code that is compatible with external C code. +Attribute name matching and aliasing +------------------------------------ + +Sometimes the type's C struct as specified in ``object_struct_name`` may use +different labels for the fields. For example we may have an extension module +``foo_extension``:: + + cdef class Foo: + cdef public int field0, field1, field2; + + def __init__(self, f0, f1, f2): + self.field0 = f0 + self.field1 = f1 + self.field2 = f2 + +but a C struct in a file ``foo_nominal.h``:: + + typedef struct { + PyObject_HEAD + int f0; + int f1; + int f2; + } FooStructNominal; + +Note that the struct uses ``f0``, ``f1``, ``f2`` but they are ``field0``, +``field1``, and ``field2`` in ``Foo``. We are given this situation, including +a header file with that struct, and we wish to write a function to sum the +values. If we write an extension module ``wrapper``:: + + cdef extern from "foo_nominal.h": + + ctypedef class foo_extension.Foo [object FooStructNominal]: + cdef: + int field0 + int field1 + int feild2 + + def sum(Foo f): + return f.field0 + f.field1 + f.field2 + +then ``wrapper.sum(f)`` (where ``f = foo_extension.Foo(1, 2, 3)``) will still +use the C-API equivalent of:: + + return f.__getattr__('field0') + + f.__getattr__('field1') + + f.__getattr__('field1') + +instead of the desired C equivalent of ``return f->f0 + f->f1 + f->f2``. We can +alias the fields by using:: + cdef extern from "foo_nominal.h": + + ctypedef class foo_extension.Foo [object FooStructNominal]: + cdef: + int field0 "f0" + int field1 "f1" + int field2 "f2" + + def sum(Foo f) except -1: + return f.field0 + f.field1 + f.field2 + +and now Cython will replace the slow ``__getattr__`` with direct C access to +the FooStructNominal fields. This is useful when directly processing Python +code. No changes to Python need be made to achieve significant speedups, even +though the field names in Python and C are different. Of course, one should +make sure the fields are equivalent. + Implicit importing ------------------ diff --git a/docs/src/userguide/external_C_code.rst b/docs/src/userguide/external_C_code.rst index d8d6eb867..80764b253 100644 --- a/docs/src/userguide/external_C_code.rst +++ b/docs/src/userguide/external_C_code.rst @@ -219,6 +219,7 @@ same applies equally to union and enum declarations. | } Foo; | | | +-------------------------+---------------------------------------------+-----------------------------------------------------------------------+ +See also use of :ref:`external_extension_types`. Note that in all the cases below, you refer to the type in Cython code simply as :c:type:`Foo`, not ``struct Foo``. diff --git a/tests/run/ext_attr_getter.srctree b/tests/run/ext_attr_getter.srctree new file mode 100644 index 000000000..6fdf7503c --- /dev/null +++ b/tests/run/ext_attr_getter.srctree @@ -0,0 +1,109 @@ +PYTHON setup.py build_ext --inplace +PYTHON -c "import runner" + +######## setup.py ######## + +from Cython.Build.Dependencies import cythonize +from distutils.core import setup + +# force the build order +setup(ext_modules= cythonize("foo_extension.pyx")) + +setup(ext_modules = cythonize("getter*.pyx")) + +######## foo_nominal.h ######## + +#include <Python.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + PyObject_HEAD + int f0; + int f1; + int f2; +} FooStructNominal; + +#ifdef __cplusplus +} +#endif + +######## foo_extension.pyx ######## + +cdef class Foo: + cdef public int field0, field1, field2; + + def __init__(self, f0, f1, f2): + self.field0 = f0 + self.field1 = f1 + self.field2 = f2 + +cdef get_field0(Foo f): + return f.field0 + +cdef get_field1(Foo f): + return f.field1 + +cdef get_field2(Foo f): + return f.field2 + +# A pure-python class that disallows direct access to fields +class OpaqueFoo(Foo): + + @property + def field0(self): + raise AttributeError('no direct access to field0') + + @property + def field1(self): + raise AttributeError('no direct access to field1') + + @property + def field2(self): + raise AttributeError('no direct access to field2') + + +######## getter0.pyx ######## + +# Access base Foo fields from C via aliased field names + +cdef extern from "foo_nominal.h": + + ctypedef class foo_extension.Foo [object FooStructNominal]: + cdef: + int field0 "f0" + int field1 "f1" + int field2 "f2" + +def sum(Foo f): + # the f.__getattr__('field0') is replaced in c by f->f0 + return f.field0 + f.field1 + f.field2 + +######## runner.py ######## + +import foo_extension, getter0 + +foo = foo_extension.Foo(23, 123, 1023) + +assert foo.field0 == 23 +assert foo.field1 == 123 +assert foo.field2 == 1023 + +ret = getter0.sum(foo) +assert ret == foo.field0 + foo.field1 + foo.field2 + +opaque_foo = foo_extension.OpaqueFoo(23, 123, 1023) + +# C can access the fields through the aliases +opaque_ret = getter0.sum(opaque_foo) +assert opaque_ret == ret +try: + # Python cannot access the fields + f0 = opaque_ret.field0 + assert False +except AttributeError as e: + pass + + |