First, PyRun_String
(or anything that can simulate eval
or exec
) can of course do it, but that seems like cheating; you're not building a generator function in C, you're building one in Python and then calling that in C.
Anyway, the reason you can't figure out how to build a generator function with the C API is that there's no C API to do it. Or, rather, there is a C API to build generators out of CPython frame objects running Python generator code objects (and to build generator functions out of generator code objects, but that part you can even do from Python; it's just the types.FunctionType
constructor), but that won't do you any good. (Unless you just want to write C code that builds Python bytecode for a generator, which would be cheating just as much as PyRun_String
, and more work.)
So, if you want to build a generator function in C, you have to do it manually. It is clearly possible to do this, as proved by the fact that Cython can do it (up to some limit--e.g., inspect.isgeneratorfunction
and inspect.isgenerator
will return False
on gen_func
and gen_func()
). But it's not easy, and I'm not sure what it gets you.
The core problem is that CPython implements generators by freezing CPython frames and passing them around (hence the API). C code doesn't use CPython frames, it uses the C stack. (Even if you used setjmp
/longjmp
and explicit stack copying to build C coroutines, you'd be fighting with the way CPython itself uses the C stack.)
So, the only viable option I can think of it to build an iterator class (which this answer shows how to do) and then implement the rest of the generator protocol on top of that. It'll be basically the same as implementing the generator protocol in Python, but storing your state on your PyObject struct instead of in your object dict, just like translating any other class to C.
If you want to see what Cython does, it's essentially that, although you have to wade through a lot of boilerplate to see it. Create a file genpyx.pyx
:
def gen_func():
yield None
Then cythonize genpyx.pyx
, and look at the created genpyx.c
file. (Look for __pyx_gb_6genpyx_2generator
, and most of the other stuff right near it.) Despite Cython having a mechanism to partially fake up frames so it can do tracebacks through Cython code with Python on either end, it's still storing all the state explicitly in a struct it passes around through the functions, just as you'd have to. Cython does support two of the quasi-documented generator attributes gi_running
and gi_yieldfrom
, which is a nice idea, but it can't fake gi_frame
and gi_code
(any more than extension functions try to fake __code__
), and it doesn't fake being an instance of types.GeneratorType
(which you could sort of do, but it would be as dangerous as with any other non-heap type).
And meanwhile, if your simulated generator doesn't have any use the value of yield
, what's the point in implementing a send
that takes and ignores an argument, checks that an otherwise-unnecessary first-run flag is set, and then does the same thing as __next__
? Trying to implement as much of the generator protocol as possible is necessary if you're building Cython and need something to compile Cython generator bodies to, but YAGNI if you're just translating things like gen_func
manually.