Die pythonischste Möglichkeit, globale Konfigurationsvariablen in config.py bereitzustellen? [geschlossen]

98

In meiner endlosen Suche nach überkomplizierten einfachen Dingen erforsche ich die 'Pythonic'-Methode, um globale Konfigurationsvariablen innerhalb der typischen' config.py ' bereitzustellen , die in Python-Ei-Paketen zu finden ist.

Der traditionelle Weg (aah, good ol ' #define !) Ist wie folgt:

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

Daher werden globale Variablen auf eine der folgenden Arten importiert:

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

oder:

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

Es ist sinnvoll, kann aber manchmal etwas chaotisch sein, insbesondere wenn Sie versuchen, sich die Namen bestimmter Variablen zu merken. Außerdem kann die Bereitstellung eines Konfigurationsobjekts mit Variablen als Attributen flexibler sein. Also, eine Leitung von der Einnahme bpython config.py Datei, kam ich mit:

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)])

und eine 'config.py', die die Klasse importiert und wie folgt lautet:

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'

und wird folgendermaßen verwendet:

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))

Dies scheint eine lesbarere, aussagekräftigere und flexiblere Methode zum Speichern und Abrufen globaler Variablen in einem Paket zu sein.

Die lahmste Idee überhaupt? Was ist die beste Vorgehensweise, um mit diesen Situationen umzugehen? Wie können Sie globale Namen und Variablen in Ihrem Paket speichern und abrufen?

Rigel Di Scala
quelle
3
Sie haben hier bereits eine Entscheidung getroffen, die vielleicht gut ist oder nicht. Die Konfiguration selbst kann auf verschiedene Arten gespeichert werden, wie JSON, XML, verschiedene Grammatiken für * nixes und Windows und so weiter. Je nachdem, wer die Konfigurationsdatei schreibt (ein Tool, ein Mensch, welcher Hintergrund?), Sind möglicherweise unterschiedliche Grammatiken vorzuziehen. In den meisten Fällen ist es möglicherweise keine gute Idee, die Konfigurationsdatei in derselben Sprache zu schreiben, die Sie für Ihr Programm verwenden, da sie dem Benutzer zu viel Leistung gibt (was Sie selbst sein könnten, aber Sie selbst erinnern sich möglicherweise nicht an alles, was möglich ist einige Monate vorher schief gehen).
Erikbwork
4
Oft schreibe ich eine JSON-Konfigurationsdatei. Es kann einfach in Python-Strukturen eingelesen und auch von einem Tool erstellt werden. Es scheint die größte Flexibilität zu haben und die einzigen Kosten sind einige Zahnspangen, die für den Benutzer ärgerlich sein könnten. Ich habe jedoch nie ein Ei geschrieben. Vielleicht ist das der Standardweg. In diesem Fall ignoriere einfach meinen obigen Kommentar.
Erikbwork
1
Sie können "vars (self)" anstelle von "self .__ dict __. Keys ()" verwenden
Karlisson
1
Mögliches Duplikat von Was ist die beste Vorgehensweise bei der Verwendung einer Einstellungsdatei in Python? Sie antworten: "Es gibt viele Möglichkeiten, und es gibt bereits einen Bikeshed-Thread. Config.py ist gut, es sei denn, Sie kümmern sich um die Sicherheit."
Nikana Reklawyks
python-box
entwickelt

Antworten:

4

Ich habe das einmal gemacht. Letztendlich fand ich meine vereinfachte basicconfig.py für meine Bedürfnisse angemessen. Sie können einen Namespace mit anderen Objekten übergeben, auf die bei Bedarf verwiesen werden soll. Sie können auch zusätzliche Standardeinstellungen aus Ihrem Code übergeben. Außerdem werden die Attribut- und Zuordnungsstilsyntax demselben Konfigurationsobjekt zugeordnet.

Keith
quelle
6
Die basicconfig.pyDatei, auf die verwiesen wird, scheint auf github.com/kdart/pycopia/blob/master/core/pycopia/…
Paul M Furley
Ich weiß, dass dies ein paar Jahre alt ist, aber ich bin ein Anfänger und ich denke, diese Konfigurationsdatei ist im Wesentlichen das, wonach ich suche (vielleicht zu fortgeschritten), und ich würde es gerne besser verstehen. Muss ich die Initialisierung nur ConfigHoldermit einem Diktat von Konfigurationen bestehen, die ich festlegen und zwischen Modulen übergeben möchte?
Jinx
@ Jinx Zu diesem Zeitpunkt würde ich eine YAML-Datei und PyYAML für die Konfiguration verwenden (und verwende sie derzeit). Ich verwende auch ein Drittanbieter-Modul namens confitund es unterstützt das Zusammenführen mehrerer Quellen. Es ist Teil eines neuen devtest.config- Moduls.
Keith
56

Wie wäre es einfach mit den eingebauten Typen wie folgt:

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

Sie würden wie folgt auf die Werte zugreifen:

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

Wenn Sie bereit sind, das Potenzial für die Berechnung von Ausdrücken in Ihrem Konfigurationsbaum zu opfern, können Sie YAML verwenden und eine besser lesbare Konfigurationsdatei wie die folgende erhalten:

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

Verwenden Sie eine Bibliothek wie PyYAML , um die Konfigurationsdatei bequem zu analysieren und darauf zuzugreifen

blubb
quelle
Normalerweise möchten Sie jedoch unterschiedliche Konfigurationsdateien haben und haben daher keine Konfigurationsdaten in Ihrem Code. "Config" wäre also eine externe JSON / YAML-Datei, die Sie jedes Mal, wenn Sie darauf zugreifen möchten, in jeder einzelnen Klasse von der Festplatte laden müssen. Ich glaube, die Frage ist, "einmal zu laden" und globalen Zugriff auf die geladenen Daten zu haben. Wie würden Sie das mit der von Ihnen vorgeschlagenen Lösung machen?
Masi
3
wenn nur etwas existieren würde, um die Daten im Speicher zu halten ^^
Film
16

Ich mag diese Lösung für kleine Anwendungen :

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")

Und dann ist die Verwendung:

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

.. Sie sollten es mögen, weil:

  • verwendet Klassenvariablen (kein Objekt zum Weitergeben / kein Singleton erforderlich),
  • verwendet gekapselte integrierte Typen und sieht aus wie ein Methodenaufruf App,
  • hat die Kontrolle über die Unveränderlichkeit der einzelnen Konfigurationen , veränderbare Globals sind die schlimmste Art von Globals .
  • fördert den konventionellen und gut benannten Zugriff / die Lesbarkeit in Ihrem Quellcode
  • ist eine einfache Klasse, erzwingt jedoch einen strukturierten Zugriff . Eine Alternative ist die Verwendung @property, erfordert jedoch mehr variablen Verarbeitungscode pro Element und ist objektbasiert.
  • erfordert nur minimale Änderungen , um neue Konfigurationselemente hinzuzufügen und die Veränderbarkeit festzulegen.

--Edit-- : Für große Anwendungen ist es besser, Werte in einer YAML-Datei (dh Eigenschaften) zu speichern und diese als unveränderliche Daten einzulesen (dh die Antwort von blubb / ohaal ). Für kleine Anwendungen ist diese Lösung einfacher.

pds
quelle
9

Wie wäre es mit Klassen?

# config.py
class MYSQL:
    PORT = 3306
    DATABASE = 'mydb'
    DATABASE_TABLES = ['tb_users', 'tb_groups']

# main.py
from config import MYSQL

print(MYSQL.PORT) # 3306
Heiser
quelle
8

Ähnlich wie bei Blubbs Antwort. Ich schlage vor, sie mit Lambda-Funktionen zu erstellen, um den Code zu reduzieren. So was:

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

Dies riecht jedoch so, als ob Sie eine Klasse machen möchten.

Oder, wie MarkM feststellte, Sie könnten verwenden 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
quelle
2
passist ein unglücklicher Variablenname, da es sich auch um ein Schlüsselwort handelt.
Thomas Schreiter
Oh ja ... Ich habe gerade dieses blöde Beispiel zusammengestellt. Ich werde den Namen ändern
Cory-G
Für diese Art von Ansatz könnten Sie eine Klasse anstelle des mkDictLambda in Betracht ziehen . Wenn wir unsere Klasse aufrufen User, werden Ihre "config" -Wörterbuchschlüssel so etwas wie initialisiert {'st3v3': User('password','blonde','Steve Booker')}. Wenn sich Ihr "Benutzer" in einer userVariablen befindet, können Sie auf deren Eigenschaften als user.hairusw. zugreifen .
Andrew Palmer
Wenn Ihnen dieser Stil gefällt, können Sie auch collection.namedtuple verwenden . User = namedtuple('User', 'passwd hair name'); config = {'st3v3': User('password', 'blonde', 'Steve Booker')}
MarkM
7

Eine kleine Variation von Huskys Idee, die ich benutze. Erstellen Sie eine Datei mit dem Namen "Globals" (oder was auch immer Sie möchten) und definieren Sie dann mehrere Klassen darin als solche:

#globals.py

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

class runtime :
    debug = False
    output = 'stdio'

Wenn Sie dann zwei Codedateien c1.py und c2.py haben, können beide oben stehen

import globals as gl

Jetzt kann der gesamte Code auf Werte zugreifen und diese als solche festlegen:

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

Menschen vergessen, dass Klassen existieren, selbst wenn niemals ein Objekt instanziiert wird, das Mitglied dieser Klasse ist. Und Variablen in einer Klasse, denen nicht 'self' vorangestellt ist. werden für alle Instanzen der Klasse gemeinsam genutzt, auch wenn keine vorhanden sind. Sobald 'Debug' durch einen Code geändert wurde, sieht jeder andere Code die Änderung.

Wenn Sie es als gl importieren, können Sie über mehrere solcher Dateien und Variablen verfügen, mit denen Sie auf Codedateien, Funktionen usw. zugreifen und Werte festlegen können, ohne dass die Gefahr einer Namespace-Kollision besteht.

Dies fehlt ein Teil der cleveren Fehlerprüfung anderer Ansätze, ist aber einfach und leicht zu befolgen.

eSurfsnake
quelle
1
Es ist nicht ratsam, ein Modul zu benennen globals, da es sich um eine integrierte Funktion handelt, die mit jedem Symbol im aktuellen globalen Bereich ein Diktat zurückgibt. Darüber hinaus empfiehlt PEP8 CamelCase (mit allen Großbuchstaben in Akronymen) für Klassen (dh DBInfo) und Großbuchstaben mit Unterstrichen für die sogenannten Konstanten (dh DEBUG).
Nuno André
1
Danke @ NunoAndré für den Kommentar, bis ich ihn gelesen habe, dachte ich, dass diese Antwort etwas Seltsames mit sich bringt globals. Der Autor sollte den Namen ändern
oglop
Dieser Ansatz ist mein Ziel. Ich sehe jedoch viele Ansätze, die die Leute als "die besten" bezeichnen. Können Sie einige Mängel bei der Implementierung von config.py wie folgt angeben?
Yash Nag
5

Seien wir ehrlich, wir sollten wahrscheinlich in Betracht ziehen, eine von Python Software Foundation gepflegte Bibliothek zu verwenden:

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

Konfigurationsbeispiel: (INI-Format, aber JSON verfügbar)

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

[bitbucket.org]
User = hg

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

Codebeispiel:

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

Global zugänglich machen:

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

Nachteile:

  • Unkontrollierter global veränderlicher Zustand.
pds
quelle
Es ist nicht sinnvoll, die INI-Datei zu verwenden, wenn Sie if-Anweisungen in Ihren anderen Dateien anwenden müssen, um die Konfiguration zu ändern. Es wäre besser, stattdessen config.py zu verwenden, aber wenn sich die Werte nicht ändern und Sie sie einfach aufrufen und verwenden, stimme ich der Verwendung der Datei.ini zu.
Kourosh
3

Überprüfen Sie das IPython-Konfigurationssystem, das über Traitlets implementiert wurde, auf die Typdurchsetzung, die Sie manuell durchführen.

Hier ausschneiden und einfügen, um den SO-Richtlinien zu entsprechen, damit nicht nur Links gelöscht werden, wenn sich der Inhalt von Links im Laufe der Zeit ändert.

Dokumentation der Traitlets

Hier sind die Hauptanforderungen, die unser Konfigurationssystem haben soll:

Unterstützung für hierarchische Konfigurationsinformationen.

Vollständige Integration mit Befehlszeilenoptionsparsern. Oft möchten Sie eine Konfigurationsdatei lesen, aber dann einige der Werte mit Befehlszeilenoptionen überschreiben. Unser Konfigurationssystem automatisiert diesen Prozess und ermöglicht die Verknüpfung jeder Befehlszeilenoption mit einem bestimmten Attribut in der Konfigurationshierarchie, das überschrieben wird.

Konfigurationsdateien, die selbst gültiger Python-Code sind. Dies erreicht viele Dinge. Erstens ist es möglich, Logik in Ihre Konfigurationsdateien einzufügen, die Attribute basierend auf Ihrem Betriebssystem, Netzwerk-Setup, Python-Version usw. festlegt. Zweitens verfügt Python über eine supereinfache Syntax für den Zugriff auf hierarchische Datenstrukturen, nämlich den regulären Attributzugriff (Foo). Bar.Bam.name). Drittens erleichtert die Verwendung von Python Benutzern das Importieren von Konfigurationsattributen von einer Konfigurationsdatei in eine andere. Viertens gibt es, obwohl Python dynamisch typisiert ist, Typen, die zur Laufzeit überprüft werden können. Somit ist eine 1 in einer Konfigurationsdatei die Ganzzahl '1', während eine '1' eine Zeichenfolge ist.

Eine vollautomatische Methode, um die Konfigurationsinformationen zur Laufzeit an die Klassen zu senden, die sie benötigen. Das Schreiben von Code, der eine Konfigurationshierarchie durchläuft, um ein bestimmtes Attribut zu extrahieren, ist schmerzhaft. Wenn Sie komplexe Konfigurationsinformationen mit Hunderten von Attributen haben, möchten Sie weinen.

Typprüfung und -validierung, bei der nicht die gesamte Konfigurationshierarchie vor der Laufzeit statisch angegeben werden muss. Python ist eine sehr dynamische Sprache und Sie wissen nicht immer alles, was beim Starten eines Programms konfiguriert werden muss.

Um dies zu erreichen, definieren sie grundsätzlich 3 Objektklassen und ihre Beziehungen zueinander:

1) Konfiguration - im Grunde ein ChainMap / Basic-Diktat mit einigen Verbesserungen für das Zusammenführen.

2) Konfigurierbar - Basisklasse zur Unterklasse aller Dinge, die Sie konfigurieren möchten.

3) Anwendung - Objekt, das instanziiert wird, um eine bestimmte Anwendungsfunktion auszuführen, oder Ihre Hauptanwendung für Einzweck-Software.

In ihren Worten:

Anwendung: Anwendung

Eine Anwendung ist ein Prozess, der einen bestimmten Job ausführt. Die naheliegendste Anwendung ist das ipython-Befehlszeilenprogramm. Jede Anwendung liest eine oder mehrere Konfigurationsdateien und einen einzelnen Satz von Befehlszeilenoptionen und erstellt dann ein Hauptkonfigurationsobjekt für die Anwendung. Dieses Konfigurationsobjekt wird dann an die konfigurierbaren Objekte übergeben, die von der Anwendung erstellt werden. Diese konfigurierbaren Objekte implementieren die eigentliche Logik der Anwendung und wissen, wie sie sich anhand des Konfigurationsobjekts selbst konfigurieren.

Anwendungen haben immer ein Protokollattribut, das ein konfigurierter Protokollierer ist. Dies ermöglicht eine zentralisierte Protokollierungskonfiguration pro Anwendung. Konfigurierbar: Konfigurierbar

Eine konfigurierbare Klasse ist eine reguläre Python-Klasse, die als Basisklasse für alle Hauptklassen in einer Anwendung dient. Die konfigurierbare Basisklasse ist leicht und macht nur eines.

Diese konfigurierbare Version ist eine Unterklasse von HasTraits, die sich selbst konfigurieren kann. Merkmale auf Klassenebene mit den Metadaten config = True werden zu Werten, die über die Befehlszeile und die Konfigurationsdateien konfiguriert werden können.

Entwickler erstellen konfigurierbare Unterklassen, die die gesamte Logik in der Anwendung implementieren. Jede dieser Unterklassen verfügt über eigene Konfigurationsinformationen, die steuern, wie Instanzen erstellt werden.

jLi
quelle