¿Cuál es la mejor práctica de Python para importar y ofrecer características opcionales?

Estoy escribiendo una pieza de software sobre Github. Es básicamente un icono de bandeja con algunas características adicionales. Quiero proporcionar un código de trabajo sin tener que hacer que el usuario instale lo que son esencialmente dependencias para funciones opcionales y realmente no quiero importar cosas que no voy a usar, así que pensé que un código como este sería ” buena solución”:

---- IN LOADING FUNCTION ---- features = [] for path in sys.path: if os.path.exists(os.path.join(path, 'pynotify')): features.append('pynotify') if os.path.exists(os.path.join(path, 'gnomekeyring.so')): features.append('gnome-keyring') #user dialog to ask for stuff #notifications available, do you want them enabled? dlg = ConfigDialog(features) if not dlg.get_notifications(): features.remove('pynotify') service_start(features ...) ---- SOMEWHERE ELSE ------ def service_start(features, other_config): if 'pynotify' in features: import pynotify #use pynotify... 

Hay algunos problemas sin embargo. Si un usuario formatea su máquina e instala la versión más reciente de su sistema operativo y vuelve a implementar esta aplicación, las características desaparecen repentinamente sin previo aviso. La solución es presentar esto en la ventana de configuración:

 if 'pynotify' in features: #gtk checkbox else: #gtk label reading "Get pynotify and enjoy notification pop ups!" 

Pero si esto es así, un mac, ¿cómo sé que no estoy enviando al usuario a una caza de ganso salvaje en busca de una dependencia que nunca puedan llenar?

El segundo problema es el:

 if os.path.exists(os.path.join(path, 'gnomekeyring.so')): 

problema. ¿Puedo asegurarme de que el archivo siempre se llame gnomekeyring.so en todas las distribuciones de Linux?

¿Cómo prueban otras personas estas características? El problema con lo básico.

 try: import pynotify except: pynotify = disabled 

es que el código es global, estos pueden estar desordenados e incluso si el usuario no quiere Pynotify … está cargado de todos modos.

Entonces, ¿qué piensa la gente que es la mejor manera de resolver este problema?

Es posible que desee echar un vistazo al módulo imp , que básicamente hace lo que usted hace manualmente arriba. Así que primero puede buscar un módulo con find_module() y luego cargarlo a través de load_module() o simplemente importándolo (después de verificar la configuración).

Y por cierto, si se usa excepto: siempre le agregaré una excepción específica (aquí, ImportError) para no detectar accidentalmente errores no relacionados.

El método try: no necesita ser global, se puede usar en cualquier ámbito y, por lo tanto, los módulos se pueden “cargar de forma perezosa” en tiempo de ejecución. Por ejemplo:

 def foo(): try: import external_module except ImportError: external_module = None if external_module: external_module.some_whizzy_feature() else: print("You could be using a whizzy feature right now, if you had external_module.") 

Cuando se ejecuta el script, no se intentará cargar external_module . La primera vez que se llama a foo() , external_module se carga (si está disponible) y se inserta en el ámbito local de la función. Las llamadas subsiguientes a foo() reinsertan external_module en su scope sin necesidad de volver a cargar el módulo.

En general, es mejor dejar que Python maneje la lógica de importación, ya que lo ha estado haciendo durante un tiempo. 🙂

Una forma de manejar el problema de las diferentes dependencias para diferentes características es implementar las características opcionales como complementos. De esa manera, el usuario tiene control sobre qué funciones se activan en la aplicación, pero no es responsable de rastrear las dependencias por sí misma. Esa tarea se maneja en el momento de la instalación de cada complemento.