Dans ma quête sans fin de complication exagérée de choses simples, je cherche la manière la plus «pythonique» de fournir des variables de configuration globales à l’intérieur du «config.py» typique présent dans les paquets Python Egg.
La manière traditionnelle (aah, good ol ' #define !) Est la suivante:
MYSQL_PORT = 3306
MYSQL_DATABASE = 'mydb'
MYSQL_DATABASE_TABLES = ['tb_users', 'tb_groups']
Par conséquent, les variables globales sont importées de l'une des manières suivantes:
from config import *
dbname = MYSQL_DATABASE
for table in MYSQL_DATABASE_TABLES:
print table
ou:
import config
dbname = config.MYSQL_DATABASE
assert(isinstance(config.MYSQL_PORT, int))
Cela a du sens, mais cela peut parfois être un peu compliqué, surtout lorsque vous essayez de vous rappeler les noms de certaines variables. De plus, fournir un objet 'configuration'}, avec variables en tant qu'attributs, pourrait être plus flexible. Ainsi, en prenant exemple sur le fichier bpython config.py, j’ai trouvé:
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)])
et un 'config.py' qui importe la classe et se lit comme suit:
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'
et est utilisé de cette façon:
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))
Ce qui semble un moyen plus lisible, expressif et flexible de stocker et d'extraire des variables globales dans un paquet.
La plus belle idée jamais? Quelle est la meilleure pratique pour faire face à ces situations? Quelle est votre manière de stocker et de récupérer les noms globaux et les variables dans votre paquet?
Je l'ai fait une fois. En fin de compte, j'ai trouvé mon système simplifié basicconfig.py adéquat pour mes besoins. Vous pouvez passer un espace de noms avec d'autres objets pour qu'il puisse s'y référer si vous en avez besoin. Vous pouvez également transmettre des valeurs par défaut supplémentaires à partir de votre code. Il mappe également la syntaxe d'attribut et de style de mappage sur le même objet de configuration.
Pourquoi ne pas utiliser les types intégrés comme ceci:
config = {
"mysql": {
"user": "root",
"pass": "secret",
"tables": {
"users": "tb_users"
}
# etc
}
}
Vous accéderiez aux valeurs comme suit:
config["mysql"]["tables"]["users"]
Si vous êtes prêt à sacrifier le potentiel de calcul d'expressions dans votre arbre de configuration, vous pouvez utiliser YAML et obtenir un fichier de configuration plus lisible comme celui-ci:
mysql:
- user: root
- pass: secret
- tables:
- users: tb_users
et utiliser une librairie comme PyYAML pour analyser et accéder au fichier de configuration de manière conventionnelle
Similaire à la réponse de blubb. Je suggère de les construire avec des fonctions lambda pour réduire le code. Comme ça:
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
Cela sent cependant que vous voudrez peut-être faire un cours.
Ou, comme MarkM l’a noté, vous pouvez utiliser 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
Que diriez-vous d'utiliser des cours?
# config.py
class MYSQL:
PORT = 3306
DATABASE = 'mydb'
DATABASE_TABLES = ['tb_users', 'tb_groups']
# main.py
from config import MYSQL
print(MYSQL.PORT) # 3306
Une petite variation sur l'idée de Husky que j'utilise. Créez un fichier appelé 'globals' (ou celui que vous préférez), puis définissez-y plusieurs classes, comme suit:
#globals.py
class dbinfo : # for database globals
username = 'abcd'
password = 'xyz'
class runtime :
debug = False
output = 'stdio'
Ensuite, si vous avez deux fichiers de code c1.py et c2.py, les deux peuvent avoir en haut
import globals as gl
Désormais, tout le code peut accéder et définir des valeurs, telles que:
gl.runtime.debug = False
print(gl.dbinfo.username)
Les gens oublient que les classes existent, même si aucun objet instancié appartenant à cette classe n'est instancié. Et les variables d'une classe qui ne sont pas précédées de «self». sont partagés entre toutes les instances de la classe, même s'il n'y en a pas. Une fois que le «débogage» est modifié par un code quelconque, tous les autres codes voient le changement.
En l'important en tant que gl, vous pouvez avoir plusieurs fichiers et variables de ce type qui vous permettent d'accéder aux valeurs des fichiers de code, des fonctions, etc., et de les définir, sans risque de collision avec les espaces de noms.
Cela manque d'une partie de la vérification d'erreur intelligente des autres approches, mais est simple et facile à suivre.
veuillez consulter le système de configuration IPython, implémenté via des traitlets pour le type de mise en application que vous effectuez manuellement.
Couper et coller ici pour respecter les consignes de SO selon lesquelles il ne faut pas simplement supprimer des liens, car leur contenu change au fil du temps.
Voici les principales exigences que nous souhaitions pour notre système de configuration:
Prise en charge des informations de configuration hiérarchique.
Intégration complète avec les analyseurs d'options de ligne de commande. Souvent, vous souhaitez lire un fichier de configuration, puis remplacer certaines des valeurs par des options de ligne de commande. Notre système de configuration automatise ce processus et permet à chaque option de ligne de commande d'être liée à un attribut particulier de la hiérarchie de configuration à remplacer.
Les fichiers de configuration qui sont eux-mêmes du code Python valide. Cela accomplit beaucoup de choses. Premièrement, il devient possible d’insérer dans vos fichiers de configuration une logique définissant les attributs en fonction de votre système d’exploitation, de votre configuration réseau, de la version Python, etc. Bar.Bam.name). Troisièmement, l'utilisation de Python permet aux utilisateurs d'importer facilement des attributs de configuration d'un fichier de configuration à un autre. Quatrièmement, même si Python est typé de manière dynamique, il existe des types qui peuvent être vérifiés lors de l'exécution. Ainsi, un 1 dans un fichier de configuration est l’entier «1», tandis qu'un «1» est une chaîne.
Une méthode entièrement automatisée pour obtenir les informations de configuration pour les classes qui en ont besoin au moment de l'exécution. L'écriture de code qui parcourt une hiérarchie de configuration pour extraire un attribut particulier est pénible. Lorsque vous disposez d'informations de configuration complexes avec des centaines d'attributs, vous avez envie de pleurer.
La vérification de type et la validation qui n'exigent pas que la hiérarchie de configuration entière soit spécifiée statiquement avant l'exécution. Python est un langage très dynamique et vous ne savez pas toujours tout ce qui doit être configuré au démarrage d’un programme.
Pour y parvenir, ils définissent en gros 3 classes d'objets et leurs relations les unes aux autres:
1) Configuration - Fondamentalement un ChainMap/dict avec quelques améliorations pour la fusion.
2) Configurable - la classe de base pour sous-classer tout ce que vous souhaitez configurer.
3) Application - objet instancié pour exécuter une fonction d'application spécifique ou votre application principale pour un logiciel à usage unique.
Dans leurs mots:
Application: application
Une application est un processus qui effectue un travail spécifique. L'application la plus évidente est le programme de ligne de commande ipython. Chaque application lit un ou plusieurs fichiers de configuration et un seul ensemble d'options de ligne de commande, puis génère un objet de configuration principal pour l'application. Cet objet de configuration est ensuite transmis aux objets configurables créés par l'application. Ces objets configurables implémentent la logique réelle de l'application et savent se configurer étant donné l'objet de configuration.
Les applications ont toujours un attribut de journal qui est un enregistreur configuré. Cela permet une configuration de journalisation centralisée par application . Configurable: Configurable
Une configurable est une classe Python standard qui sert de classe de base pour toutes les classes principales d'une application. La classe de base configurable est légère et ne fait qu'une chose.
This Configurable est une sous-classe de HasTraits qui sait se configurer. Les traits de niveau classe avec les métadonnées config = True deviennent des valeurs pouvant être configurées à partir de la ligne de commande et des fichiers de configuration.
Les développeurs créent des sous-classes configurables qui implémentent toute la logique de l'application. Chacune de ces sous-classes a ses propres informations de configuration qui contrôlent la manière dont les instances sont créées.