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

98

En mi búsqueda interminable de complicar demasiado 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 (¡ah, bueno #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 formas:

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 complicado, 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 iniciativa 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.

¿La idea más tonta de todas? ¿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?

Rigel Di Scala
fuente
3
Ya tomó una decisión aquí que podría ser buena o no. La configuración en sí se puede almacenar de diferentes formas, como JSON, XML, diferentes gramáticas para * nixes y Windows, etc. Dependiendo de quién escriba el archivo de configuración (una herramienta, un ser humano, ¿qué antecedentes?), Pueden ser preferibles diferentes gramáticas. La mayoría de las veces, puede que no sea una buena idea dejar que el archivo de configuración se escriba en el mismo idioma que usa para su programa, porque le da demasiado poder al usuario (lo que podría ser usted mismo, pero es posible que usted mismo no recuerde todo lo que puede salir mal algunos meses antes).
erikbwork
4
A menudo termino escribiendo un archivo de configuración JSON. Puede leerse en estructuras de Python fácilmente y también ser creado por una herramienta. Parece tener la mayor flexibilidad y el único costo son algunos aparatos que pueden resultar molestos para el usuario. Sin embargo, nunca escribí un huevo. Quizás esa sea la forma estándar. En ese caso, simplemente ignore mi comentario anterior.
erikbwork
1
Puede usar "vars (self)" en lugar de "self .__ dict __.
Keys
1
Posible duplicado de ¿Cuál es la mejor práctica para usar un archivo de configuración en Python? Responden: "Son posibles muchas formas, y ya existe un hilo en bicicleta. Config.py es bueno a menos que te preocupes por la seguridad".
Nikana Reklawyks
Terminé usando python-box, vea esta respuesta
evolucionó el

Respuestas:

5

Hice eso una vez. Al final, encontré mi basicconfig.py simplificado adecuado para mis necesidades. Puede pasar un espacio de nombres con otros objetos para que haga referencia si es necesario. También puede pasar valores predeterminados adicionales de su código. También asigna la sintaxis de estilo de asignación y atributo al mismo objeto de configuración.

Keith
fuente
6
El basicconfig.pyarchivo al que se hace referencia parece haberse movido a github.com/kdart/pycopia/blob/master/core/pycopia/…
Paul M Furley
Sé que esto tiene algunos años, pero soy un principiante y creo que este archivo de configuración es esencialmente lo que estoy buscando (tal vez demasiado avanzado), y me gustaría entenderlo mejor. ¿Simplemente paso initialize ConfigHoldercon un dictado de configuraciones que me gustaría configurar y pasar entre módulos?
Jinx
@Jinx En este punto, usaría (y estoy usando actualmente) un archivo YAML y PyYAML para la configuración. También utilizo un módulo de terceros llamado confity admite la fusión de múltiples fuentes. Es parte de un nuevo módulo devtest.config .
Keith
56

¿Qué tal si usamos los tipos integrados como este?

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

Accederá 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, 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 use una biblioteca como PyYAML para analizar y acceder convenientemente al archivo de configuración

lloriquear
fuente
Pero normalmente desea tener diferentes archivos de configuración y, por lo tanto, no tener ningún dato de configuración dentro de su código. Entonces, ´config´ sería un archivo JSON / YAML externo que debes cargar desde el disco cada vez que quieras acceder a él, en cada clase. Creo que la pregunta es "cargar una vez" y tener acceso global a los datos cargados. ¿Cómo haría eso con la solución que sugirió?
masi
3
si solo existiera algo para mantener los datos en la memoria ^^
cinatic
16

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 gustará porque:

  • usa variables de clase (no hay objeto para pasar / no se requiere singleton),
  • utiliza tipos integrados encapsulados y parece (es) una llamada a un método App,
  • tiene control sobre la inmutabilidad de la configuración individual , los globales mutables son el peor tipo de globales .
  • promueve el acceso / legibilidad convencional y bien nombrado en su código fuente
  • es una clase simple pero impone acceso estructurado , se puede usar una alternativa @property, pero 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 leerlos como datos inmutables es un mejor enfoque (es decir, la respuesta de blubb / ohaal ). Para aplicaciones pequeñas, esta solución anterior es más simple.

pds
fuente
9

¿Qué tal 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
Fornido
fuente
8

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 señaló MarkM, 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
Cory-G
fuente
3
passes un nombre de variable desafortunado, ya que también es una palabra clave.
Thomas Schreiter
Oh, sí ... Acabo de reunir este tonto ejemplo. Cambiaré el nombre
Cory-G
Para este tipo de enfoque, podría considerar una clase en lugar de mkDictlambda. Si llamamos a nuestra clase User, las claves del diccionario "config" se inicializarían algo así como {'st3v3': User('password','blonde','Steve Booker')}. Cuando su "usuario" está en una uservariable, puede acceder a sus propiedades como user.hair, etc.
Andrew Palmer
Si te gusta este estilo, también puedes optar por usar collections.namedtuple . User = namedtuple('User', 'passwd hair name'); config = {'st3v3': User('password', 'blonde', 'Steve Booker')}
MarkM
7

Una pequeña variación de la idea de Husky que utilizo. Cree un archivo llamado 'globals' (o lo que quiera) y luego defina múltiples clases en él, como tal:

#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 variables en una clase que no están precedidas por "yo". se comparten en todas las instancias de la clase, incluso si no hay ninguna. Una vez que cualquier código cambia "depuración", el resto del código ve el cambio.

Al importarlo como gl, puede tener varios 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 de espacios de nombres.

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

eSurfsnake
fuente
1
Se desaconseja nombrar un módulo globals, ya que es una función incorporada que devuelve un dict con cada símbolo en el ámbito global actual. Además, PEP8 recomienda CamelCase (con todas las mayúsculas en acrónimos) para las clases (es decir DBInfo) y mayúsculas con guiones bajos para las llamadas constantes (es decir DEBUG).
Nuno André
1
Gracias @ NunoAndré por el comentario, hasta que lo leí estaba pensando que esta respuesta hace algo extraño globals, el autor debería cambiar el nombre
oglop
Este enfoque es mi destino. Sin embargo, veo muchos enfoques que la gente dice que es "el mejor". ¿Puede indicar algunas deficiencias para implementar config.py como este?
Yash Nag
5

Seamos honestos, probablemente deberíamos considerar el uso de una biblioteca mantenida por Python Software Foundation :

https://docs.python.org/3/library/configparser.html

Ejemplo de configuración: (formato ini, pero JSON disponible)

[DEFAULT]
ServerAliveInterval = 45
Compression = yes
CompressionLevel = 9
ForwardX11 = yes

[bitbucket.org]
User = hg

[topsecret.server.com]
Port = 50022
ForwardX11 = no

Ejemplo de código:

>>> import configparser
>>> config = configparser.ConfigParser()
>>> config.read('example.ini')
>>> config['DEFAULT']['Compression']
'yes'
>>> config['DEFAULT'].getboolean('MyCompression', fallback=True) # get_or_else

Haciéndolo accesible a nivel mundial:

import configpaser
class App:
 __conf = None

 @staticmethod
 def config():
  if App.__conf is None:  # Read only once, lazy.
   App.__conf = configparser.ConfigParser()
   App.__conf.read('example.ini')
  return App.__conf

if __name__ == '__main__':
 App.config()['DEFAULT']['MYSQL_PORT']
 # or, better:
 App.config().get(section='DEFAULT', option='MYSQL_PORT', fallback=3306)
 ....

Desventajas:

  • Estado mutable global incontrolado .
pds
fuente
No es útil usar el archivo .ini si necesita aplicar instrucciones if en sus otros archivos para cambiar la configuración. Sería mejor usar config.py en su lugar, pero si los valores no cambian, y simplemente lo llama y lo usa, estoy de acuerdo con el uso del archivo .ini.
Kourosh
3

por favor, consulte el sistema de configuración de IPython, implementado a través de traitlets para el tipo de aplicación que está haciendo manualmente.

Cortar y pegar aquí para cumplir con las pautas de SO para no solo eliminar enlaces cuando el contenido de los enlaces cambia con el tiempo.

documentación de traitlets

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

Soporte para 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 anula 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 comandos se vincule a un atributo particular en la jerarquía de configuración que anulará.

Archivos de configuración que son en sí mismos código Python válido. Esto logra muchas cosas. Primero, 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. En segundo lugar, Python tiene una sintaxis súper simple para acceder a estructuras de datos jerárquicas, es decir, acceso regular a atributos (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. Cuarto, aunque Python se escribe 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 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 recorra una jerarquía de configuración para extraer un atributo particular es doloroso. Cuando tiene información de configuración compleja con cientos de atributos, esto le da ganas de llorar.

Verificación y validación de tipos que no requieren 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 sabes todo lo que se debe configurar cuando se inicia un programa.

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

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

2) Configurable: clase base para crear una subclase de todas las cosas que desea configurar.

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

En sus palabras:

Aplicación: Aplicación

Una aplicación es un proceso que realiza un trabajo específico. La aplicación más obvia es el programa 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 comando 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 a sí mismos 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 Python regular que sirve como clase base para todas las clases principales de 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 metadata 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.

jLi
fuente