Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Added support for cpdef enum (take 2) #232

Closed
wants to merge 1 commit into from

2 participants

@nnemkin

This is a resurrection of PR45.

I've started with @vitek's patch, fixed .pxd enum processing and added more tests.

To answer comments in the original thread:

  • Enums as Python classes is a completely different feature. cpdef enum simply exposes C enumeration values, which are always module scoped. Therefore, modname.VALUE_NAME has the same meaning in Cython (C level) and Python.
  • Cython type system and its handling of enum types is not affected in any way.

My use case: I have two modules full of numeric constants (~500 in total) that are used throughout the project. Currently it is impossible to expose them to Python without duplicating every definition. Also, it is impossible to declare Python values in the same module as C enum values, due to name conflict. (I have considered using globals().extend(VALUE=..., etc) but opted for this patch instead.)

Docs will be written if/when the change is approved.

@scoder

Would "from enum cimport *" reexport the enums?

Owner

I'm just wondering how users can control in which cases an enum appears in the API and when it doesn't. I think this should be visible in the tests and also be documented explicitly.

I also think that I would be happier with a solution that allowed re-exporting the enums even when they are declared as a simple "cdef enum". Having to copy the declaration just because someone else didn't declare it "cpdef" is just as bad as what we currently have.

I've just checked and this implementation does materialize cimported enums, although it should not!
cpdef enum should only be materialized in its own module (meaning that cpdef enum in a .pxd file makes no sense without a corresponding .pyx file.)

Exposing external enums to Python is a common problem, but cpdef enums do not not aim to solve it. Their behavior and usage is consistent with cpdef functions (after I fix reexport bug) and, given the simplicity of implementation, I think their existence is justified.

Arbitraty enum reexport ("materialization") will require new syntax and new semantics, which ups the complexity quite a bit. Language design is never easy.
Actually, I see automatic materialization of C declarations as the domain of wrapper generators. If enums become automated, why not functions and structs? Some lightweight wrappers are 99% python-to-c call bolierplate.
I don't mind Cython building such features into the language, but it requires considerable design and implementation effort. When viewed from this angle, materialization is not related to cpdef enums at all. Sure, there are tehcnical similarities, but semantic difference is huge.

BTW, for reasons unknown to me, in-modue enums are materialized when they are declared public. cpdef enum can be seen as a variant of this functionality.

@scoder scoder referenced this pull request from a commit
@robertwb robertwb cpdef enum tests ef9c1c3
@scoder scoder referenced this pull request
Closed

Add support for cpdef enum #45

@scoder
Owner

Superseded by ef9c1c3

@scoder scoder closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on May 21, 2013
  1. @nnemkin
This page is out of date. Refresh to see the latest.
View
11 Cython/Compiler/CodeGeneration.py
@@ -17,12 +17,12 @@ class ExtractPxdCode(VisitorTransform):
"""
def __call__(self, root):
- self.funcs = []
+ self.code_statements = []
self.visitchildren(root)
- return (StatListNode(root.pos, stats=self.funcs), root.scope)
+ return (StatListNode(root.pos, stats=self.code_statements), root.scope)
def visit_FuncDefNode(self, node):
- self.funcs.append(node)
+ self.code_statements.append(node)
# Do not visit children, nested funcdefnodes will
# also be moved by this action...
return node
@@ -30,3 +30,8 @@ def visit_FuncDefNode(self, node):
def visit_Node(self, node):
self.visitchildren(node)
return node
+
+ def visit_CEnumDefNode(self, node):
+ if node.visibility == 'public' or node.is_overridable:
+ self.code_statements.append(node)
+ return node
View
4 Cython/Compiler/Nodes.py
@@ -442,7 +442,7 @@ def analyse_expressions(self, env):
return self
def generate_execution_code(self, code):
- pass
+ self.body.generate_execution_code(code) # for cpdef enums
def annotate(self, code):
self.body.annotate(code)
@@ -1354,7 +1354,7 @@ def analyse_expressions(self, env):
return self
def generate_execution_code(self, code):
- if self.visibility == 'public' or self.api:
+ if self.visibility == 'public' or self.api or self.is_overridable:
temp = code.funcstate.allocate_temp(PyrexTypes.py_object_type, manage_ref=True)
for item in self.entry.enum_values:
code.putln("%s = PyInt_FromLong(%s); %s" % (
View
2  Cython/Compiler/ParseTreeTransforms.py
@@ -1757,7 +1757,7 @@ def visit_CBaseTypeNode(self, node):
return None
def visit_CEnumDefNode(self, node):
- if node.visibility == 'public':
+ if node.visibility == 'public' or node.is_overridable:
return node
else:
return None
View
7 Cython/Compiler/Parsing.py
@@ -2591,8 +2591,8 @@ def p_cdef_statement(s, ctx):
elif s.sy == 'IDENT' and s.systring in struct_enum_union:
if ctx.level not in ('module', 'module_pxd'):
error(pos, "C struct/union/enum definition not allowed here")
- if ctx.overridable:
- error(pos, "C struct/union/enum cannot be declared cpdef")
+ if ctx.overridable and s.systring != "enum":
+ error(pos, "C struct/union cannot be declared cpdef")
return p_struct_enum(s, pos, ctx)
elif s.sy == 'IDENT' and s.systring == 'fused':
return p_fused_definition(s, pos, ctx)
@@ -2649,7 +2649,8 @@ def p_c_enum_definition(s, pos, ctx):
return Nodes.CEnumDefNode(
pos, name = name, cname = cname, items = items,
typedef_flag = ctx.typedef_flag, visibility = ctx.visibility,
- api = ctx.api, in_pxd = ctx.level == 'module_pxd')
+ api = ctx.api, in_pxd = ctx.level == 'module_pxd',
+ is_overridable = ctx.overridable)
def p_c_enum_line(s, ctx, items):
if s.sy != 'pass':
View
85 tests/run/cpdef_enum.srctree
@@ -0,0 +1,85 @@
+PYTHON setup.py build_ext --inplace
+PYTHON -m doctest test.py
+
+######## setup.py ########
+
+from Cython.Build.Dependencies import cythonize
+from distutils.core import setup
+
+setup(ext_modules=cythonize("*.pyx"))
+
+######## enum.h ########
+
+enum {
+ ANONYMOUS = 1
+};
+
+enum Enum {
+ NAMED = 1
+};
+
+typedef enum {
+ TYPEDEF = 1
+} TypedefEnum;
+
+#define DEFINED 1
+
+######## enum.pxd ########
+
+cpdef enum:
+ PY_ANONYMOUS = 1
+
+cpdef enum PyEnum:
+ PY_NAMED = 1
+
+cdef extern from "enum.h":
+ cpdef enum:
+ ANONYMOUS
+
+ cpdef enum Enum:
+ NAMED
+
+ cpdef enum:
+ DEFINED
+
+ cpdef enum _TypedefEnum:
+ TYPEDEF
+ ctypedef _TypedefEnum TypedefEnum
+
+cdef inline void func(PyEnum a, Enum b, TypedefEnum c):
+ pass
+
+######## enum.pyx ########
+
+# enums are defined in .pxd
+
+######## enum_pyx.pyx ########
+
+# enums are defined in .pyx
+include "enum.pxd"
+
+######## test.py ########
+"""
+>>> enum.ANONYMOUS, enum.NAMED, enum.TYPEDEF, enum.PY_ANONYMOUS, enum.PY_NAMED
+(1, 1, 1, 1, 1)
+>>> enum.Enum
+Traceback (most recent call last):
+ ...
+AttributeError: 'module' object has no attribute 'Enum'
+>>> enum.PyEnum
+Traceback (most recent call last):
+ ...
+AttributeError: 'module' object has no attribute 'PyEnum'
+
+>>> enum_pyx.ANONYMOUS, enum_pyx.NAMED, enum_pyx.TYPEDEF, enum_pyx.PY_ANONYMOUS, enum_pyx.PY_NAMED
+(1, 1, 1, 1, 1)
+>>> enum_pyx.Enum
+Traceback (most recent call last):
+ ...
+AttributeError: 'module' object has no attribute 'Enum'
+>>> enum_pyx.PyEnum
+Traceback (most recent call last):
+ ...
+AttributeError: 'module' object has no attribute 'PyEnum'
+"""
+import enum, enum_pyx
Something went wrong with that request. Please try again.