7

I am writing a Python wrapper for a C library using the cffi.

The C library has to be initialized and shut down. Also, the cffi needs some place to save the state returned from ffi.dlopen().

I can see two paths here:

Either I wrap this whole stateful business in a class like this

class wrapper(object):
    def __init__(self):
        self.c = ffi.dlopen("mylibrary")
        self.c.initialize()
    def __del__(self):
        self.c.terminate()

Or I provide two global functions that hide the state in a global variable

def initialize():
    global __library 
    __library = ffi.dlopen("mylibrary")
    __library.initialize()
def terminate():
    __library.terminate()
    del __library

The first path is somewhat cumbersome in that it requires the user to always create an object that really serves no other purpose other than managing the library state. On the other hand, it makes sure that terminate() is actually called every time.

The second path seems to result in a somewhat easier API. However, it exposes some hidden global state, which might be a bad thing. Also, if the user forgets to call terminate(), the C library is not unloaded correctly (which is not a big problem on the C side).

Which one of these paths would be more pythonic?

bastibe
  • 16,551
  • 28
  • 95
  • 126
  • 1
    Any reason to not just initialize the library when the module is imported (and store the necessary reference(s) in module globals)? – kindall Jun 27 '13 at 15:28
  • @kindall how would I call `terminate()` then? – bastibe Jun 28 '13 at 06:24
  • 1
    You said it's not a big deal if that doesn't get called. But if it needs calling, then the only way to do it reliably is to have the user of your library call it. You can't count on `__del__` anyway. A context manager might make sense, though... – kindall Jun 28 '13 at 13:00

1 Answers1

4

Exposing a wrapper object only makes sense in python if the library actually supports something like multiple instances in one application. If it doesn't support that or it's not really relevant go for kindall's suggestion and just initialize the library when imported and add an atexit handler for cleanup.

Adding wrappers around a stateless api or even an api without support for keeping different sets of state is not really pythonic and would raise expectations that different instances have some kind of isolation.

Example code:

import atexit

# Normal library initialization
__library = ffi.dlopen("mylibrary")
__library.initialize()

# Private library cleanup function
def __terminate():
    __library.terminate()
# register function to be called on clean interpreter termination
atexit.register(__terminate)

For more details about atexit this question has some more details, as has the python documentation of course.

Community
  • 1
  • 1
textshell
  • 1,746
  • 14
  • 21