Skip to content

[BUG] Not exposing class decorator wrapping cdef function to Python #4322

@Bluenix2

Description

@Bluenix2

Describe the bug
I am making a convenient extension class wrapping a bitfield, but Cython is not revealing my descriptor class when wrapping a cdef function.

To Reproduce
This has been converted to Pyrex syntax to be explicit (could be reproduced)

cdef class flag:
    """A flag value, works similar to a property by using descriptors."""

    cdef long int mask

    def __init__(self, func) -> None:
        self.mask = func(None)

    def __get__(self, instance, owner):
        """Called when this is accessed through an attribute."""
        if instance is None:
            return self  # Acessed through a class directly

        return (instance.value & self.mask) == self.mask

    def __set__(self, instance, value):
        if value == True:
            instance.value |= self.mask
        else:
            instance.value &= ~self.mask


cdef class Simple:
    """A simple bitfield"""

    cdef int value

    def __init__(self, value):
        self.value = value

    @flag
    cdef one(self):
        return 1 << 1

    @flag
    cdef two(self):
        return 1 << 2

On the latest stable release, this did not compile with the following error:

Error compiling Cython file:
------------------------------------------------------------
...
    cdef int value

    def __init__(self, value):
        self.value = value

   ^
------------------------------------------------------------

flags.pyx:31:4: Cdef functions/classes cannot take arbitrary decorators.

On master branch (installed through pip install -U git+https://github.com/cython/cython) this compiles, but. The issue now becomes that the one and two attributes is not available to Python as demonstrated below:

>>> import flags
>>> s = flags.Simple(1)
>>> dir(s)
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__pyx_vtable__', '__reduce__', '__reduce_cython__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__setstate_cython__', '__sizeof__', '__str__', '__subclasshook__']

Expected behavior
I would expect my Simple class to expose 2 instances of my flag extension type called one and two. When I do s.one I would expect it to call __get__. When the flag extension type is initialized, it calls the C function and gets the bit mask.
This is from Python:

class flag:
    """A flag value, works similar to a property by using descriptors."""

    def __init__(self, func) -> None:
        self.mask = func(None)

    def __get__(self, instance, owner):
        """Called when this is accessed through an attribute."""
        if instance is None:
            return self  # Acessed through a class directly

        return (instance.value & self.mask) == self.mask

    def __set__(self, instance, value):
        if value == True:
            instance.value |= self.mask
        else:
            instance.value &= ~self.mask


class Simple:
    """A simple bitfield"""

    def __init__(self, value):
        self.value = value

    @flag
    def one(self):
        return 1 << 1

    @flag
    def two(self):
        return 1 << 2

s = Simple(6)
print(type(Simple.one))  # <class '__main__.flag'>
print(s.one)  # True
print(s.two)  # True

Environment (please complete the following information):

  • OS: Windows
  • Python version 3.9.5
  • Cython version 3.0.0.a9

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions