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
On 11/25/10 11:31 , Lennart Regebro wrote:
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.
The second. #ifdefs in code make code hard to follow and tend to lead to problems. FWIW the Linux kernel tree has a similar policy: they hide all differences behind macros and helper functions.
Wichert.
On Thu, Nov 25, 2010 at 11:37, Wichert Akkerman wichert@wiggy.net wrote:
On 11/25/10 11:31 , Lennart Regebro wrote:
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.
The second. #ifdefs in code make code hard to follow and tend to lead to problems. FWIW the Linux kernel tree has a similar policy: they hide all differences behind macros and helper functions.
OK, only one voice, but it agrees with me, so I'll go for this. Thanks!
//Lennart