How can I extend a built-in type in cython?

Question:

I’m trying to extend the basic float in python with cython additional methods. I have a python implementation and I know I could create my own extended type by keeping an internal float value. But I’m trying to keep the same code base for interpreted and compiled code using the benefits of pure python mode.

So how can this be translated into cython?

class Bearing(float):
    def __new__(cls, value: float):
        value %= 360
        return super().__new__(cls, value)

    def __add__(self: Bearing, other: Bearing) -> Bearing:
        return Bearing(float(self) + float(other))

The obvious adding of @cython.cclass would not work because __new__ can’t be used in cython extended types. As far as I know __cinit__ is not appropriate because its not meant to return an instance.

The manual mentions that it is possible, although an extern would have to be declared. But I have not been able to find any examples of how this could be done.

Any help? Thanks!

Not only does Ahmed AEK’s answer below works. But there is a performance penalty

@cython.cclass
class _CythonFloatSubclass(float):
    def get(self) -> float:
        return self

    def __add__(self: _CythonFloatSubclass, other: _CythonFloatSubclass) -> _CythonFloatSubclass:
        return CythonFloatSubclass(cython.cast(cython.float, self) + cython.cast(cython.float, other))

class CythonFloatSubclass(_CythonFloatSubclass):
    def __new__(cls, value: float):
        value %= 360
        return super().__new__(cls, value)

This version instantiates 20% faster than the cython compiled regular float subclass, which seems surprising to me. (54ns vs 64ns)

Yet adding two of instances is 10% slower than adding two instances of a cython class that stores its float value in an attribute, rather than subclassing float. (445ns for CythonFloatSubclass vs 400ns)

Even more worryingly, the interpreted python version of a class that holds a float value is faster than the cclass version of the same thing. (375ns)

To answer the comments: There will be more methods. In fact the main one is substraction, that instantiates a BearingDelta class, also a float subclass.

And the main concern is speed. I am simulating thousands of aicraft trajectories. I’ve concentrated on having a correct algorithm first, for which Bearing, BearingDelta, RangeBearing and Point classes help enormously even though they just substitute floats and tuples of floats. But now comes the optimization and with interpreted python it all becomes very slow because of all the instancing of new objects.

(I did a second edit because in my first test I was instancing the _CythonFloatSubclass in the __add__ method, which doesn’t go through the __new__ method of CythonFloatSubclass)

Asked By: JuanT

||

Answers:

from the open issue on the problem support __new__() in extension types https://github.com/cython/cython/issues/799

it seems you cannot use the __new__ method in a cdef class, and have to use it in a wrapper python class.

import cython

@cython.cclass
class _Bearing(float):
    def __add__(self: Bearing, other: Bearing):
        return Bearing(float(self) + float(other))

class Bearing(_Bearing):
    def __new__(cls, value: float):
        value %= 360
        return super().__new__(cls, value)
Answered By: Ahmed AEK