¿La forma más Pythonic de proporcionar variables de configuración global en config.py?

En mi interminable búsqueda de complicar las cosas simples, estoy investigando la forma más ‘Pythonic’ de proporcionar variables de configuración global dentro del típico ‘ config.py ‘ que se encuentra en los paquetes de huevos de Python.

La forma tradicional (aah, good ol ‘ #define !) Es la siguiente:

MYSQL_PORT = 3306 MYSQL_DATABASE = 'mydb' MYSQL_DATABASE_TABLES = ['tb_users', 'tb_groups'] 

Por lo tanto, las variables globales se importan de una de las siguientes maneras:

 from config import * dbname = MYSQL_DATABASE for table in MYSQL_DATABASE_TABLES: print table 

o:

 import config dbname = config.MYSQL_DATABASE assert(isinstance(config.MYSQL_PORT, int)) 

Tiene sentido, pero a veces puede ser un poco desordenado, especialmente cuando intentas recordar los nombres de ciertas variables. Además, proporcionar un objeto de ‘configuración’ , con variables como atributos , podría ser más flexible. Entonces, tomando una ventaja del archivo bpython config.py, se me ocurrió:

 class Struct(object): def __init__(self, *args): self.__header__ = str(args[0]) if args else None def __repr__(self): if self.__header__ is None: return super(Struct, self).__repr__() return self.__header__ def next(self): """ Fake iteration functionality. """ raise StopIteration def __iter__(self): """ Fake iteration functionality. We skip magic attribues and Structs, and return the rest. """ ks = self.__dict__.keys() for k in ks: if not k.startswith('__') and not isinstance(k, Struct): yield getattr(self, k) def __len__(self): """ Don't count magic attributes or Structs. """ ks = self.__dict__.keys() return len([k for k in ks if not k.startswith('__')\ and not isinstance(k, Struct)]) 

y un ‘config.py’ que importa la clase y dice lo siguiente:

 from _config import Struct as Section mysql = Section("MySQL specific configuration") mysql.user = 'root' mysql.pass = 'secret' mysql.host = 'localhost' mysql.port = 3306 mysql.database = 'mydb' mysql.tables = Section("Tables for 'mydb'") mysql.tables.users = 'tb_users' mysql.tables.groups = 'tb_groups' 

y se usa de esta manera:

 from sqlalchemy import MetaData, Table import config as CONFIG assert(isinstance(CONFIG.mysql.port, int)) mdata = MetaData( "mysql://%s:%s@%s:%d/%s" % ( CONFIG.mysql.user, CONFIG.mysql.pass, CONFIG.mysql.host, CONFIG.mysql.port, CONFIG.mysql.database, ) ) tables = [] for name in CONFIG.mysql.tables: tables.append(Table(name, mdata, autoload=True)) 

Lo que parece una forma más legible, expresiva y flexible de almacenar y obtener variables globales dentro de un paquete.

¿Alguna vez te has equivocado? ¿Cuál es la mejor práctica para hacer frente a estas situaciones? ¿Cuál es su forma de almacenar y obtener nombres y variables globales dentro de su paquete?

Lo hice una vez. En última instancia, encontré mi basicconfig.py simplificado adecuado para mis necesidades. Puede pasar un espacio de nombres con otros objetos para que haga referencia si lo necesita. También puede pasar valores predeterminados adicionales de su código. También asigna la syntax de atributos y estilos de mapeo al mismo objeto de configuración.

¿Qué tal solo usando los tipos incorporados como este?

 config = { "mysql": { "user": "root", "pass": "secret", "tables": { "users": "tb_users" } # etc } } 

Accederás a los valores de la siguiente manera:

 config["mysql"]["tables"]["users"] 

Si está dispuesto a sacrificar el potencial de calcular expresiones dentro de su árbol de configuración, podría usar YAML y terminar con un archivo de configuración más legible como este:

 mysql: - user: root - pass: secret - tables: - users: tb_users 

y use una biblioteca como PyYAML para analizar convenientemente y acceder al archivo de configuración

Similar a la respuesta de blubb. Sugiero construirlos con funciones lambda para reducir el código. Me gusta esto:

 User = lambda passwd, hair, name: {'password':passwd, 'hair':hair, 'name':name} #Col Username Password Hair Color Real Name config = {'st3v3' : User('password', 'blonde', 'Steve Booker'), 'blubb' : User('12345678', 'black', 'Bubb Ohaal'), 'suprM' : User('kryptonite', 'black', 'Clark Kent'), #... } #... config['st3v3']['password'] #> password config['blubb']['hair'] #> black 

Sin embargo, esto huele como si quisieras hacer una clase.

O, como MarkM señaló, usted podría usar namedtuple

 from collections import namedtuple #... User = namedtuple('User', ['password', 'hair', 'name']} #Col Username Password Hair Color Real Name config = {'st3v3' : User('password', 'blonde', 'Steve Booker'), 'blubb' : User('12345678', 'black', 'Bubb Ohaal'), 'suprM' : User('kryptonite', 'black', 'Clark Kent'), #... } #... config['st3v3'].password #> passwd config['blubb'].hair #> black 

Me gusta esta solución para pequeñas aplicaciones :

 class App: __conf = { "username": "", "password": "", "MYSQL_PORT": 3306, "MYSQL_DATABASE": 'mydb', "MYSQL_DATABASE_TABLES": ['tb_users', 'tb_groups'] } __setters = ["username", "password"] @staticmethod def config(name): return App.__conf[name] @staticmethod def set(name, value): if name in App.__setters: App.__conf[name] = value else: raise NameError("Name not accepted in set() method") 

Y luego el uso es:

 if __name__ == "__main__": # from config import App App.config("MYSQL_PORT") # return 3306 App.set("username", "hi") # set new username value App.config("username") # return "hi" App.set("MYSQL_PORT", "abc") # this raises NameError 

.. te debe gustar porque:

  • utiliza variables de clase (no se requiere ningún objeto para pasar / no se requiere singleton),
  • utiliza tipos integrados encapsulados y se parece a (es) un método de llamada en la App ,
  • tiene control sobre la inmutabilidad de la configuración individual, los globales mutables son el peor tipo de globales .
  • promueve accesibilidad / legibilidad convencional y bien nombrada en su código fuente
  • es una clase simple pero impone un acceso estructurado , una alternativa es usar @property , pero eso requiere más código de manejo variable por elemento y está basado en objetos.
  • requiere cambios mínimos para agregar nuevos elementos de configuración y establecer su mutabilidad.

–Editar– : para aplicaciones grandes, almacenar valores en un archivo YAML (es decir, propiedades) y leer que en datos inmutables es un mejor enfoque (es decir, la respuesta de blubb / ohaal ). Para aplicaciones pequeñas, esta solución de arriba es más simple.

¿Qué hay de usar clases?

 # config.py class MYSQL: PORT = 3306 DATABASE = 'mydb' DATABASE_TABLES = ['tb_users', 'tb_groups'] # main.py from config import MYSQL print(MYSQL.PORT) # 3306 

Una pequeña variación de la idea de Husky que uso. Haga un archivo llamado ‘globales’ (o lo que quiera) y luego defina múltiples clases en él, como tales:

 #globals.py class dbinfo : # for database globals username = 'abcd' password = 'xyz' class runtime : debug = False output = 'stdio' 

Luego, si tiene dos archivos de código c1.py y c2.py, ambos pueden tener en la parte superior

 import globals as gl 

Ahora todo el código puede acceder y establecer valores, como tales:

 gl.runtime.debug = False print(gl.dbinfo.username) 

La gente olvida que las clases existen, incluso si no se crea una instancia de ningún objeto que sea miembro de esa clase. Y las variables en una clase que no están precedidas por ‘self’. se comparten en todas las instancias de la clase, incluso si no hay ninguna. Una vez que cualquier código cambia la “depuración”, todos los demás códigos verán el cambio.

Al importarlo como gl, puede tener múltiples archivos y variables de este tipo que le permiten acceder y establecer valores en archivos de código, funciones, etc., pero sin peligro de colisión en el espacio de nombres.

Esto carece de la comprobación inteligente de errores de otros enfoques, pero es simple y fácil de seguir.

Verifique el sistema de configuración de IPython, implementado a través de traitlets para el tipo de ejecución que está haciendo manualmente.

Corte y pegue aquí para cumplir con las directrices de SO para no solo eliminar enlaces, ya que el contenido de los enlaces cambia con el tiempo.

documentación de traitlets

Estos son los principales requisitos que queríamos que nuestro sistema de configuración tuviera:

Soporte para información de configuración jerárquica.

Integración completa con los analizadores de opciones de línea de comandos. A menudo, desea leer un archivo de configuración, pero luego anular algunos de los valores con las opciones de la línea de comandos. Nuestro sistema de configuración automatiza este proceso y permite que cada opción de línea de comando se vincule a un atributo particular en la jerarquía de configuración que anulará.

Archivos de configuración que a su vez son códigos Python válidos. Esto logra muchas cosas. Primero, es posible poner lógica en sus archivos de configuración que establece atributos según su sistema operativo, configuración de red, versión de Python, etc. Bar.Bam.name). En tercer lugar, el uso de Python facilita a los usuarios la importación de atributos de configuración de un archivo de configuración a otro. Cuarto, a pesar de que Python está tipificado dinámicamente, tiene tipos que pueden verificarse en tiempo de ejecución. Por lo tanto, un 1 en un archivo de configuración es el entero ‘1’, mientras que un ‘1’ es una cadena.

Un método totalmente automatizado para llevar la información de configuración a las clases que la necesitan en tiempo de ejecución. Escribir código que recorre una jerarquía de configuración para extraer un atributo particular es doloroso. Cuando tienes información de configuración compleja con cientos de atributos, esto hace que quieras llorar.

La comprobación y validación de tipos que no requieren que se especifique estáticamente toda la jerarquía de configuración antes del tiempo de ejecución. Python es un lenguaje muy dynamic y no siempre sabe todo lo que necesita configurarse cuando se inicia un progtwig.

Para lograr esto, básicamente definen 3 clases de objetos y sus relaciones entre sí:

1) Configuración: básicamente un dictado ChainMap / basic con algunas mejoras para la fusión.

2) Configurable: clase base para subclasificar todas las cosas que desea configurar.

3) Aplicación: objeto que se crea una instancia para realizar una función de aplicación específica, o su aplicación principal para software de un solo propósito.

En sus palabras:

Aplicación: Aplicación

Una aplicación es un proceso que hace un trabajo específico. La aplicación más obvia es el progtwig de línea de comandos ipython. Cada aplicación lee uno o más archivos de configuración y un solo conjunto de opciones de línea de comandos y luego produce un objeto de configuración maestro para la aplicación. Este objeto de configuración se pasa luego a los objetos configurables que crea la aplicación. Estos objetos configurables implementan la lógica real de la aplicación y saben cómo configurarse según el objeto de configuración.

Las aplicaciones siempre tienen un atributo de registro que es un registrador configurado. Esto permite la configuración de registro centralizado por aplicación. Configurable: Configurable

Una configurable es una clase regular de Python que sirve como una clase base para todas las clases principales en una aplicación. La clase base configurable es liviana y solo hace una cosa.

Este configurable es una subclase de HasTraits que sabe cómo configurarse. Los rasgos de nivel de clase con los metadatos config = True se convierten en valores que se pueden configurar desde la línea de comandos y los archivos de configuración.

Los desarrolladores crean subclases configurables que implementan toda la lógica en la aplicación. Cada una de estas subclases tiene su propia información de configuración que controla cómo se crean las instancias.