6

I was handed some code that looks like this:

In "header.hpp":

enum class my_enum_type {
    val1 = 0;
    ... 
}

In "header_lib.pyx":

cdef extern from "header.hpp":
    enum my_enum_type:
        val1 = 0;
        ...
...

Later in "header_lib.pyx":

def foo():
    ...
    return my_enum_type.val1

I was told this should have no problems, but that is not the case from my experience just now,and as evident in this post: Defining enums in Cython code that will be used in the C part of code.

However, it doesn't recognize "val1" by itself either if I write "return val1". What's the correct way to do this?

Community
  • 1
  • 1
Ben
  • 2,065
  • 1
  • 13
  • 19

3 Answers3

9

You can declare a enum in Cython as:

ctypedef enum options: OPT1, OPT2, OPT3

or

ctypedef enum options:
    OPT1,
    OPT2,
    OPT3

And and example could be:

def main():
    cdef options test
    test = OPT2
    f(test)

cdef void f(options inp):
    if inp == OPT1:
        print('OPT1')
    elif inp == OPT2:
        print('OPT2')
    elif inp == OPT3:
        print('OPT3')

When running main() you will see "OPT2" printed. You could pass the variable test to a C or C++ function in the same way as shown here for the cdef function.

Saullo G. P. Castro
  • 56,802
  • 26
  • 179
  • 234
  • 6
    Note that the OP asks about `enum class` not `enum`. – Bjoern Dahlgren Dec 13 '15 at 10:10
  • The problem I see with this technique is that you cannot convert from a string to the original enum class, and in the pure Python world there is no notion of `enum`, thus Python cannot easily check the object type, the enum name or the underlying value. – ibarrond Jun 15 '21 at 08:47
  • @ibarrond I haven't checked it, but I think Python recognizes the enum options as integer values. Good point though... – Saullo G. P. Castro Jun 15 '21 at 09:08
  • That's right, Cython enums are just ints at Python level. – ibarrond Jun 15 '21 at 09:52
5

To maintain the duality of Cython/C/C++ enum class and Python Enum class functionalities, I propose a solution with mirroring classes. This solution is divided in two components:

  1. On one side we have a Cython/C/C++ enum. This enum can either be originated by wrapping C/C++ code in an extern block...

     cdef extern from "header.h":
         enum my_c_enum_type:
             OPT1 = 0,
             OPT2,
             OPT3,
    

    ... or directly in Cython.

     ctypedef enum my_cy_enum_type:
         OPT1=0,    #Default value is 0
         OPT2,      #Default value is 1
         OPT3,      #Default value is 2
    
  2. On the other we have Python. Since Python 3.4, there is a supported class Enum that emulates that functionality (link to official documentation). Here we should replicate the enum elements in pure Python. You can make use of the auto function to fill the Enum just like in C/C++/Cython:

     from enum import Enum, auto
     class my_py_enum_type(Enum):
         OPT1=0
         OPT2=auto()
         OPT3=auto()
    

Now, in order to use this dual Python & Cython solution:

    # PYTHON: we use the my_py_enum_type Enum
    enum_obj = my_py_enum_type(1)        # Corresponds to OPT2
    print(enum_obj.value, enum_obj.name) # We can access the value and the name 
    #> 1, OPT2
    s='OPT2'; enum_obj = getattr(my_py_enum_type, s) # We can create them from the string 'OPT2'


    # PYTHON TO CYTHON
    def func(enum_obj):
        if not isinstance(enum_obj, my_py_enum_type):
             raise TypeError
        # Use in a cython function that accepts only my_c_enum_type Cython enum
        myCythonEnumFunc(enum_obj.value) # Convert to C/Cython enum using value
        ...

    # CYTHON TO PYTHON
    def func():
        # a Cython enum
        cdef my_c_enum_type cy_enum_obj = val1
        # creating a python enum
        return my_p_enum_type(cy_enum_obj)  # Convert using the full cy_enum
        ...

This solution translates Cython/C enums (which in Python are mere ints) into a proper Python Enum class with the same characteristics, and translates the value inside a Python Enum object into the original Cython class. You get the best of both worlds!

ibarrond
  • 6,617
  • 4
  • 26
  • 45
2

You can use cpdef to directly create a Python Enum in cython:

cpdef enum my_enum_type:
    val1 = 0
    ...

This only gets a very short mention in the docs (search for cpdef enum)

Chronial
  • 66,706
  • 14
  • 93
  • 99