[Zope-dev] I want some Python 3 C coding style guidance.

Lennart Regebro regebro at gmail.com
Thu Nov 25 05:31:37 EST 2010


I've been porting some zope.* modules that have C-extensions to Python
3, and with the C-preprocessor you have so many possibilities that I
get all confused. So I'd like some opinions. Or onions. Or something.


The big issue is the module definition, which is quite different in
Python 2 and Python 3. What I ended up with in zope.proxy was
something like this:

----------------------------------

    #if PY_MAJOR_VERSION >= 3
      static struct PyModuleDef moduledef = {
	PyModuleDef_HEAD_INIT,
	"_zope_proxy_proxy", /* m_name */
	module___doc__,      /* m_doc */
	-1,                  /* m_size */
	module_functions,    /* m_methods */
	NULL,                /* m_reload */
	NULL,                /* m_traverse */
	NULL,                /* m_clear */
	NULL,                /* m_free */
      };
    #endif

    static PyObject *
    moduleinit(void)
    {
	PyObject *m;
	
    #if PY_MAJOR_VERSION >= 3
	m = PyModule_Create(&moduledef);
    #else
	m = Py_InitModule3("_zope_proxy_proxy",
			    module_functions, module___doc__);
    #endif

	if (m == NULL)
	    return NULL;

	if (empty_tuple == NULL)
	    empty_tuple = PyTuple_New(0);

	ProxyType.tp_free = _PyObject_GC_Del;

	if (PyType_Ready(&ProxyType) < 0)
	    return NULL;

	Py_INCREF(&ProxyType);
	PyModule_AddObject(m, "ProxyBase", (PyObject *)&ProxyType);

	if (api_object == NULL) {
	    api_object = PyCObject_FromVoidPtr(&wrapper_capi, NULL);
	    if (api_object == NULL)
	    return NULL;
	}
	Py_INCREF(api_object);
	PyModule_AddObject(m, "_CAPI", api_object);
	
      return m;
    }

    #if PY_MAJOR_VERSION < 3
	PyMODINIT_FUNC
	init_zope_proxy_proxy(void)
	{
	    moduleinit();
	}
    #else
	PyMODINIT_FUNC
	PyInit__zope_proxy_proxy(void)
	{
	    return moduleinit();
	}
    #endif

----------------------------------

As you see, there are loads of #if PY_MAJOR_VERSION >= 3 in there. And
three methods (I took this from Martin v Löwis work on zope.interface)
for the module init, as they have different profiles and names in
Python 2 and Python3.
This may be seen as quite messy, and many other compatibility issues
between 2 and 3 can be handled by defining macros and using #ifndefs.
So why not do the same for the module initialization? Said and done.
This is from zope.hookable:

----------------------------------

    #if PY_MAJOR_VERSION >= 3
        #define MOD_ERROR_VAL NULL
        #define MOD_SUCCESS_VAL(val) val
        #define MOD_INIT(name) PyMODINIT_FUNC PyInit_##name(void)
        #define MOD_DEF(ob, name, doc, methods) \
            static struct PyModuleDef moduledef = { \
                PyModuleDef_HEAD_INIT, name, doc, -1, methods, }; \
            ob = PyModule_Create(&moduledef);
    #else
        #define MOD_ERROR_VAL
        #define MOD_SUCCESS_VAL(val)
        #define MOD_INIT(name) void init##name(void)
        #define MOD_DEF(ob, name, doc, methods) \
            ob = Py_InitModule3(name, methods, doc);
    #endif

    MOD_INIT(_zope_hookable)
    {
        PyObject *m;

	hookabletype.tp_new = PyType_GenericNew;
	hookabletype.tp_free = _PyObject_GC_Del;

	if (PyType_Ready(&hookabletype) < 0)
	    return MOD_ERROR_VAL;

	MOD_DEF(m, "_zope_hookable",
	    "Provide an efficient implementation for hookable objects",
	    module_methods)

	if (m == NULL) return MOD_ERROR_VAL;

	if (PyModule_AddObject(m, "hookable", \
	    (PyObject *)&hookabletype) < 0)
	    return MOD_ERROR_VAL;

	return MOD_SUCCESS_VAL(m);
    }

----------------------------------

As you see, there is one block of macro definitions in the start, and
then just one function at the bottom. Benefits are that if you have
many C-extensions you can extract the macro definitions to a separate
file. Drawbacks are that MOD_INIT looks like a function, when it is in
fact a function definition. But I don't know, maybe C-programmers are
used to that sort of thing. :-) Also, it's far from complete, MOD_DEF
doesn't support the new module_reload and module_traverse things for
example, bt maybe that's fixable.

Which style do you prefer? I'll make zope.hookable, zope.i18nmessage
and zope.proxy use the same style if we can agree on one.

//Lennart


More information about the Zope-Dev mailing list