2011-06-01 6 views
57

En mi búsqueda interminable de complicadas cosas simples, estoy investigando la forma más 'pitónica' de proporcionar variables de configuración globales dentro de la típica 'config.py' que se encuentra en los paquetes de Python.¿La forma más pitónica de proporcionar variables de configuración globales en config.py?

La forma tradicional (! Aah, buen ol' #define ) es el siguiente:

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

las variables globales Por lo tanto se importan en 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 b e un poco desordenado, especialmente cuando intenta recordar los nombres de ciertas variables. Además, proporcionar un objeto 'configuración', con variables como atributos, podría ser más flexible. Por lo tanto, tomando una ventaja de bpython archivo 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 utiliza de esta manera:

from sqlalchemy import MetaData, Table 
import config as CONFIG 

assert(isinstance(CONFIG.mysql.port, int)) 

mdata = MetaData(
    "mysql://%s:%[email protected]%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)) 

que parece una manera más fácil de leer, expresivo y flexible de almacenamiento y ir a buscar variables globales dentro de un paquete.

Lamest idea ever? ¿Cuál es la mejor práctica para hacer frente a estas situaciones? ¿Qué es su forma de almacenar y buscar nombres y variables globales dentro de su paquete?

+0

Usted ya tomó una decisión aquí que podría o no ser buena. La configuración en sí se puede almacenar de diferentes maneras, como JSON, XML, diferentes gramáticas para * nixes y Windows, y así sucesivamente. Dependiendo de quién escriba el archivo de configuración (una herramienta, un humano, ¿qué fondo?) Diferentes gramáticas podrían ser preferibles. Muy a menudo puede no ser una buena idea dejar que el archivo de configuración se escriba en el mismo idioma que usas para tu programa, ya que le da demasiada potencia al usuario (lo que podrías ser tú mismo, pero tú mismo podrías no recordar todo lo que pueda ir mal unos meses más adelante). – erikbwork

+0

A menudo termino escribiendo un archivo de configuración JSON. Se puede leer fácilmente en las estructuras de python y también se puede crear con una herramienta. Parece tener la mayor flexibilidad y el único costo son algunas llaves que pueden ser molestas para el usuario. Aunque nunca escribí un huevo. Tal vez esa es la forma estándar. En ese caso, simplemente ignora mi comentario anterior. – erikbwork

+0

Puede usar "vars (self)" en lugar de "self .__ dict __. Keys()" – Karlisson

Respuesta

5

Lo hice una vez. Finalmente encontré mi basicconfig.py simplificado adecuado para mis necesidades. Puede pasar en un espacio de nombres con otros objetos para que lo haga referencia si lo necesita. También puede pasar los valores predeterminados adicionales de su código. También asigna la sintaxis del estilo de asignación y de atributo al mismo objeto de configuración.

+4

El archivo 'basicconfig.py' al que se hace referencia parece haberse movido a https://github.com/kdart/pycopia/blob/master/core/pycopia/basicconfig.py –

43

¿Qué tal el uso de la incorporada en los tipos de la siguiente manera:

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

es posible acceder a los valores de la siguiente manera:

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

Si usted está dispuesto a sacrificar el potencial para calcular expresiones dentro de su árbol de configuración, puede 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 utilizar una biblioteca como PyYAML para analizar conventiently y acceder al archivo de configuración

6

similares a Blubb de respuesta. Me gustan los tipos integrados. Sugiero construirlos con funciones lambda si puedes.De esta manera:

mkDict = lambda passwd, hair, name: {'passwd':passwd, 'hair':hair, 'name':name} 

#Col Names:    Password  Hair Color Real Name 
config = {'st3v3' : mkDict('password', 'blonde', 'Steve Booker'), 
      'blubb' : mkDict('12345678', 'black', 'Bubb Ohaal'), 
      'suprM' : mkDict('kryptonite', 'black', 'Clark Kent'), 
      #... 
     } 

Yay, ahora usted no tiene que copiar y pegar tanto. Con comentarios, también es más fácil comparar y leer datos más tarde.

+2

' pass' es un desafortunado nombre de variable, ya que es también una palabra clave. – ThS

+0

Oh, sí ... Acabo de unir este tonto ejemplo. Cambiaré el nombre –

+0

Para este tipo de enfoque, puede considerar una clase en lugar de la lambda 'mkDict'. Si llamamos a nuestra clase 'User', sus claves de diccionario de" configuración "se inicializarían como' {'st3v3': User ('password', 'blonde', 'Steve Booker')} '. Cuando su "usuario" está en una variable de 'usuario', puede acceder a sus propiedades como 'user.hair', etc. –

0

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

Cortado y pegado aquí para cumplir con las pautas de SO para no solo eliminar enlaces, ya que el contenido de los enlaces cambia con el tiempo.

traitlets documentation

Éstos son los principales requisitos queríamos que nuestro sistema de configuración que tiene:

Support para obtener información de configuración jerárquica.

Integración completa con 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 opciones de línea de comando. 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 prevalecerá.

Archivos de configuración que a su vez son códigos válidos de Python. Esto logra muchas cosas. En primer lugar, es posible poner lógica en sus archivos de configuración que establece atributos basados ​​en su sistema operativo, configuración de red, versión de Python, etc. Segundo, Python tiene una sintaxis súper simple para acceder a estructuras de datos jerárquicas, concretamente el acceso a atributos regulares (Foo. 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. En cuarto lugar, aunque Python está tipeado dinámicamente, tiene tipos que se pueden verificar en tiempo de ejecución. Por lo tanto, un 1 en un archivo de configuración es el número entero '1', mientras que un '1' es una cadena.

Método totalmente automático para obtener la información de configuración de las clases que lo necesitan en tiempo de ejecución. Escribir código que recorre una jerarquía de configuración para extraer un atributo en particular es doloroso. Cuando tiene información de configuración compleja con cientos de atributos, esto hace que desee llorar.

Comprobación de tipos y validación que no requiere que toda la jerarquía de configuración se especifique estáticamente antes del tiempo de ejecución. Python es un lenguaje muy dinámico y no siempre se sabe todo lo que se debe configurar cuando se inicia un programa.

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

1) Configuración - básicamente un/dict básica ChainMap con algunas mejoras para la fusión.

2) Configurable: clase base a la subclase de todas las cosas que desea configurar.

3) Aplicación: objeto instanciado 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 programa de línea de comando ipython. Cada aplicación lee uno o más archivos de configuración y un único 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 dado el objeto de configuración.

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

Un 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.

Esta 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 comando 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.

3

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 a continuación, 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 

.. le gustaría porque:

  • usos variab clase les (sin objeto para pasar alrededor de/no singleton requerido),
  • usos encapsulados tipos incorporados y parece (es) una llamada de método en App,
  • tiene control sobre config individuo inmutabilidad, Los globals mutables son el peor tipo de globales.
  • promueve convencional y así llamado Acceso/legibilidad en el código fuente
  • es una clase simple pero hace cumplir un acceso estructurado, una alternativa es utilizar @property, pero que requiere código de manejo más variable por artículo y es objeto -basado.
  • requiere cambios mínimos para agregar nuevos elementos de configuración y establecer su mutabilidad.

--Edit--: Para aplicaciones de gran tamaño, el almacenamiento de valores en un archivo YAML (es decir, propiedades) y la lectura de los datos de que en tan inmutable es un mejor enfoque (es decir blubb/ohaal's answer). Para aplicaciones pequeñas, esta solución anterior es más simple.

5

¿Qué le parece usar las 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 
1

Una pequeña variación de la idea de Husky que uso. Hacer un archivo llamado 'globales' (o lo que quieras) y luego definir múltiples clases en ella, tales como:

#globals.py 

class dbinfo :  # for database globals 
    username = 'abcd' 
    password = 'xyz' 

class runtime : 
    debug = False 
    output = 'stdio' 

Entonces, 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 ajustar valores, tales como:

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

la gente olvida existan las clases, incluso si no hay ningún objeto se crea una instancia cada vez que es un miembro de esa clase. Y 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 "código de depuración" es cambiado por cualquier código, el resto del código ve el cambio.

Al importarlo como gl, puede tener varios de estos archivos y variables que le permiten acceder y establecer valores entre archivos de código, funciones, etc., pero sin peligro de colisión del espacio de nombres.

Esto carece de algunas de las comprobaciones de errores inteligentes de otros enfoques, pero es simple y fácil de seguir.

Cuestiones relacionadas