J'ai toujours utilisé des fichiers [~ # ~] json [~ # ~] pour la configuration de mes applications. J'ai commencé à les utiliser à partir du moment où j'ai codé beaucoup de Java, et maintenant je travaille principalement sur le côté serveur et la science des données Python et je ne sais pas si [~ # ~] json [~ # ~] est la bonne façon de continuer.
J'ai vu Celery utiliser de vrais fichiers Python pour la configuration. Au départ, j'étais sceptique à ce sujet. Mais l'idée d'utiliser de simples structures de données Python pour la configuration commence) de grandir sur moi. Quelques pros:
Donc, ma question est: Si je change, comment je me tire dans le pied?
Aucun utilisateur final non qualifié n'utilisera les fichiers de configuration. Toutes les modifications apportées aux fichiers de configuration sont actuellement validées dans Git et sont déployées sur nos serveurs dans le cadre d'un déploiement continu. Il n'y a aucune modification de configuration manuelle, sauf en cas d'urgence ou en cours de développement.
(J'ai envisagé [~ # ~] yaml [~ # ~] , mais quelque chose me dérange. Donc, pour le moment, il est hors Table américaine.)
L'utilisation d'un langage de script à la place d'un fichier de configuration est superbe à première vue: vous disposez de toute la puissance de ce langage et vous pouvez simplement eval()
ou import
. En pratique, il y a quelques pièges:
c'est un langage de programmation, qui doit être appris. Pour éditer la configuration, vous devez connaître suffisamment cette langue. Les fichiers de configuration ont généralement un format plus simple qui est plus difficile à se tromper.
c'est un langage de programmation, ce qui signifie que la configuration peut devenir difficile à déboguer. Avec un fichier de configuration normal, vous le regardez et voyez quelles valeurs sont fournies pour chaque propriété. Avec un script, vous devrez peut-être d'abord l'exécuter pour voir les valeurs.
c'est un langage de programmation, ce qui rend difficile de maintenir une séparation claire entre la configuration et le programme réel. Parfois, vous voulez ce type d'extensibilité, mais à ce stade, vous recherchez probablement un véritable système de plugins.
c'est un langage de programmation, ce qui signifie que la configuration peut faire tout ce que le langage de programmation peut faire. Donc, soit vous utilisez une solution sandbox qui annule une grande partie de la flexibilité du langage, soit vous placez une confiance élevée dans l'auteur de la configuration.
Donc, utiliser un script pour la configuration est probablement OK si le public de votre outil est des développeurs, par exemple Config Sphinx ou setup.py dans les projets Python. Les autres programmes avec une configuration exécutable sont des shells comme Bash et des éditeurs comme Vim.
L'utilisation d'un langage de programmation pour la configuration est nécessaire si la configuration contient de nombreuses sections conditionnelles ou si elle fournit des rappels/plugins. Utiliser un script directement au lieu de eval () - certains champs de configuration ont tendance à être plus déboguables (pensez aux traces de pile et aux numéros de ligne!).
L'utilisation directe d'un langage de programmation peut également être une bonne idée si votre configuration est si répétitive que vous écrivez des scripts pour générer automatiquement la configuration. Mais peut-être qu'un meilleur modèle de données pour la configuration pourrait supprimer le besoin d'une telle configuration explicite? Par exemple, il peut être utile que le fichier de configuration puisse contenir des espaces réservés que vous développerez ultérieurement. Une autre caractéristique parfois vue est plusieurs fichiers de configuration avec une priorité différente qui peuvent se remplacer, bien que cela pose certains problèmes.
Dans la majorité des cas, les fichiers INI, Java ou les documents YAML sont bien mieux adaptés à la configuration. Pour les modèles de données complexes, XML peut également être Comme vous l'avez noté, JSON présente certains aspects qui le rendent inapproprié en tant que fichier de configuration modifiable par l'homme, bien qu'il s'agisse d'un format d'échange de données fin.
+1 à tout dans réponse amon . Je voudrais ajouter ceci:
Vous regretterez d'utiliser le code Python comme langue de configuration la première fois que vous souhaitez importer la même configuration à partir de code écrit dans une langue différente. Par exemple, si le code fait partie de votre projet et qu'il écrit en C++ ou Ruby ou autre chose doit extraire la configuration, vous devrez lier l'interpréteur Python comme bibliothèque ou analyser la configuration) dans un coprocessus Python, tous deux maladroits, difficiles ou à surcharge élevée).
Tout le code qui importe cette configuration aujourd'hui peut être écrit en Python, et vous pensez peut-être que cela sera également vrai demain, mais le savez-vous avec certitude?
Vous avez dit que vous utiliseriez la logique (autre chose que des structures de données statiques) dans votre configuration avec parcimonie, ce qui est bien, mais s'il y en a un peu, vous aurez du mal à l'avenir à l'annuler afin que vous peut revenir à un fichier de configuration déclaratif.
[~ # ~] éditez [~ # ~] pour mémoire: plusieurs personnes ont commenté cette réponse sur la probabilité ou l'improbabilité d'un projet serait jamais complètement réécrit avec succès dans une autre langue. Il est juste de dire qu'une réécriture rétrocompatible complète est probablement rarement vue. Ce que j'avais en fait à l'esprit, c'était des morceaux du même projet (et ayant besoin d'accéder à la même configuration) qui étaient écrits dans différentes langues. Par exemple, servir la pile en C++ pour la vitesse, nettoyer la base de données par lots en Python, certains scripts Shell comme colle. Alors pensez aussi à ce cas :)
Les autres réponses sont déjà très bonnes, je vais simplement apporter mon expérience d'utilisation réelle dans quelques projets.
Ils sont pour la plupart déjà énoncés:
eval
); cela fonctionne automatiquement même pour les types de données plus complexes (dans notre programme, nous avons des points géométriques et des transformations , qui sont vides/chargés très bien via repr
/eval
);repr
. C'est évidemment une mauvaise chose .Même si vous êtes en Python, modifier le fichier de configuration à partir du code est un vrai problème, car ... eh bien, la modification du code n'est pas du tout triviale, en particulier le code qui a une syntaxe riche et qui n'est pas dans LISP ou similaire. Un de nos programmes a un fichier de configuration qui est Python, écrit à la main à l'origine, mais qui s'est avéré plus tard qu'il serait utile de manipuler via un logiciel (un paramètre particulier est une liste de choses qui est plus simple à réorganiser en utilisant une interface graphique). C'est un gros problème, car:
Comparez cela avec JSON, INI ou (Dieu ne plaise!) XML, où la représentation en mémoire peut toujours être éditée et réécrite sans perte de données (XML, où la plupart des analyseurs DOM peuvent conserver espaces blancs dans les nœuds de texte et les nœuds de commentaire) ou au moins perdre juste une mise en forme (JSON, où le format lui-même ne permet pas beaucoup plus que les données brutes que vous lisez).
Donc, comme d'habitude, il n'y a pas de solution claire; ma politique actuelle sur la question est la suivante:
si le fichier de configuration est:
un Python peut être une idée valide;
si à la place:
un format "données uniquement" peut être une meilleure idée.
Notez qu'il n'est pas nécessaire de faire un seul choix - j'ai récemment écrit une application qui utilise les deux approches. J'ai un fichier presque jamais modifié avec des paramètres manuscrits de première configuration où il y a des avantages à avoir des bonus Nice Python, et un fichier JSON pour la configuration édité à partir de l'interface utilisateur.
La question principale est: voulez-vous que votre fichier de configuration soit dans un langage complet de Turing (comme Python est) ? Si vous le souhaitez, vous pouvez également envisager d'incorporer un langage de script autre (Turing complet) comme Guile ou Lua (car il pourrait être perçu comme "plus simple" à utiliser ou à incorporer que Python est; lisez le chapitre sur Extension et incorporation de Python ). Je ne discuterai pas de cela plus loin (car d'autres réponses -eg par Amon - ont discuté de cela en profondeur) mais notez que incorporer un langage de script dans votre application est un majeur choix architectural , à considérer très tôt; Je ne recommande vraiment pas de faire ce choix plus tard!
Un exemple bien connu d'un programme configurable via des "scripts" est l'éditeur GNU emacs (ou probablement AutoCAD dans le domaine propriétaire); alors sachez que si vous acceptez les scripts, certains utilisateurs finiront par utiliser - et peut-être abuser, à votre avis - cette fonctionnalité de manière extensive et créer un script de plusieurs milliers de lignes; par conséquent, le choix d'un langage de script suffisamment bon est important.
Cependant (au moins sur les systèmes POSIX), vous pourriez juger pratique d'activer dynamiquement le fichier de configuration ( calculé au moment de l'initialisation (bien sûr, laissant la charge d'une configuration saine à votre administrateur système ou utilisateur; en fait, c'est une configuration texte qui provient d'un fichier ou d'une commande). Pour cela, vous pouvez simplement adopter la convention (et document it) qu'un chemin de fichier de configuration commençant par ex. un !
ou un |
est en fait un Shell commande que vous liriez comme pipeline . Cela laisse à votre utilisateur le choix d'utiliser le "préprocesseur" ou le "langage de script" qu'il connaît le mieux.
(vous devez faire confiance à votre utilisateur sur les problèmes de sécurité si vous acceptez une configuration calculée dynamiquement)
Ainsi, dans votre code d'initialisation, votre main
accepterait (par exemple) certains --config
argumentconfarg
et en obtiendrait FILE*configf;
De il. Si cet argument commence par !
(C'est-à-dire si (confarg[0]=='!')
....), vous utiliserez configf = popen(confarg+1, "r");
et fermerez ce canal avec pclose(configf);
. Sinon, vous utiliserez configf=fopen(confarg, "r");
et fermerez ce fichier avec fclose(configf);
(n'oubliez pas la vérification d'erreur). Voir pipe (7) , popen (3) , fopen (3) . Pour une application codée en Python lire sur os.popen , etc ...
(document également pour l'utilisateur étrange souhaitant passer un fichier de configuration nommé !foo.config
pour passer ./!foo.config
pour contourner l'astuce popen
ci-dessus)
BTW, une telle astuce n'est qu'une commodité (pour éviter de demander à l'utilisateur avancé, par exemple, de coder un script Shell pour générer une configuration fichier). Si l'utilisateur souhaite signaler un bug, il doit vous envoyer le fichier de configuration généré ...
Notez que vous pouvez également concevoir votre application avec la possibilité d'utiliser et de charger plugins au moment de l'initialisation, par ex. avec dlopen (3) (et vous devez faire confiance à votre utilisateur à propos de ce plugin). Encore une fois, ceci est une décision architecturale très importante (et vous devez définir et fournir un peu stable API et une convention sur ces plugins et votre application).
Pour une application codée dans un langage de script comme Python vous pouvez également accepter un argument de programme pour eval ou exec ou des primitives similaires. Encore une fois, les problèmes de sécurité sont alors la préoccupation de l'utilisateur (avancé) .
En ce qui concerne le format textuel de votre fichier de configuration (qu'il soit généré ou non), je pense que vous devez principalement documentez-le bien (et le choix d'un format particulier est pas c'est important; Cependant, je recommande de laisser votre utilisateur pouvoir y mettre des commentaires (ignorés)). Vous pouvez utiliser JSON (de préférence avec un analyseur JSON acceptant et ignorant les commentaires avec //
Habituel jusqu'à eol ou /*
... */
...), ou YAML ou XML , ou INI ou votre propre truc. Analyser un fichier de configuration est assez facile (et vous trouverez de nombreuses bibliothèques liées à cette tâche).
Ajout à réponse amon , avez-vous envisagé des alternatives? JSON est peut-être plus que ce dont vous avez besoin, mais les fichiers Python vous poseront probablement des problèmes à l'avenir pour les raisons mentionnées ci-dessus.
Cependant Python possède déjà un analyseur de configuration pour un langage de configuration très simple qui pourrait répondre à tous vos besoins. Le module ConfigParser
implémente un langage de configuration simple.
Je travaille depuis longtemps avec certains logiciels bien connus qui ont leurs fichiers de configuration écrits en TCL, donc l'idée n'est pas nouvelle. Cela a très bien fonctionné, car les utilisateurs qui ne connaissaient pas la langue pouvaient toujours écrire/modifier des fichiers de configuration simples en utilisant un seul set name value
, alors que les utilisateurs et développeurs plus avancés pourraient tirer des astuces sophistiquées avec cela.
Je ne pense pas que "les fichiers de configuration peuvent devenir difficiles à déboguer" est une préoccupation valable. Tant que votre application n'oblige pas les utilisateurs à écrire des scripts, vos utilisateurs peuvent toujours utiliser des affectations simples dans leurs fichiers de configuration, ce qui n'est guère plus difficile à obtenir correctement qu'en JSON ou XML.
La réécriture de la configuration est un problème, mais ce n'est pas aussi mauvais qu'il n'y paraît. La mise à jour de code arbitraire est impossible, mais le chargement de la configuration à partir d'un fichier, sa modification et sa sauvegarde le sont. Fondamentalement, si vous effectuez des scripts dans un fichier de configuration qui n'est pas en lecture seule, vous vous retrouverez simplement avec une liste équivalente de set name value
instructions une fois enregistrées. Un bon indice que cela se produira est un commentaire "ne pas modifier" au début du fichier.
Une chose à considérer est que vos fichiers de configuration ne seront pas lisibles de manière fiable par de simples outils basés sur des expressions régulières, tels que sed
, mais pour autant que je sache, ce n'est déjà pas le cas avec vos fichiers JSON actuels, donc il n'y a pas grand chose à perdre.
Assurez-vous simplement d'utiliser les techniques sandboxing appropriées lors de l'exécution de vos fichiers de configuration.
Outre tous les points valables d'autres bonnes réponses ici (wow, ils ont même mentionné le concept Turing-complete), il y a en fait quelques bonnes raisons pratiques de NE PAS utiliser un fichier Python comme configuration, même lorsque vous travaillez sur un projet Python uniquement.
Les paramètres à l'intérieur d'un fichier source Python font techniquement partie du code source exécutable, plutôt que d'un fichier de données en lecture seule. Si vous suivez cette voie, vous feriez généralement import config
, car ce type de "commodité" était probablement l'une des principales raisons pour lesquelles les gens ont commencé à utiliser un fichier Python comme configuration en premier lieu. Maintenant, vous avez tendance à valider ce fichier config.py dans votre référentiel, sinon votre utilisateur final rencontrerait une ImportError déroutante lorsqu'il tenterait d'exécuter votre programme pour la première fois.
En supposant que vous validiez réellement ce fichier config.py dans votre référentiel, les membres de votre équipe auraient probablement des paramètres différents selon l'environnement. Imaginez n jour ou l'autre un membre valide accidentellement son fichier de configuration local dans le référentiel.
Dernier point mais non le moindre, votre projet pourrait avoir des mots de passe dans le fichier de configuration. (Il s'agit d'une pratique discutable en soi, mais cela arrive quand même.) Et si votre fichier de configuration existe dans le référentiel, vous risquez de valider vos informations d'identification dans un référentiel public.
Maintenant, l'utilisation d'un fichier de configuration de données uniquement, tel que le format JSON universel, peut éviter les 3 problèmes ci-dessus, car vous pouvez raisonnablement demander à l'utilisateur de créer son propre config.json et de l'intégrer dans votre programme.
PS: Il est vrai que JSON a de nombreuses restrictions. 2 des limitations mentionnées par le PO peuvent être résolues par une certaine créativité.
Et j'ai généralement un espace réservé pour contourner la règle de virgule de fin. Comme ça:
{
"foo": 123,
"bar": 456,
"_placeholder_": "all other lines in this file can now contain trailing comma"
}