web-dev-qa-db-de.com

Die meisten Pythonic-Wege, globale Konfigurationsvariablen in config.py bereitzustellen?

Bei meiner endlosen Suche nach komplizierten Komplikationen erforsche ich die "Pythonic" -Methode, um globale Konfigurationsvariablen innerhalb der typischen " config.py " von Python Egg-Paketen bereitzustellen. 

Der traditionelle Weg (aah, good ol '#define!) Lautet 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))

Dies ist sinnvoll, kann jedoch manchmal etwas unordentlich sein, besonders wenn Sie versuchen, sich die Namen bestimmter Variablen zu merken. Außerdem könnte die Bereitstellung eines 'Konfigurations'-Objekts mit Variablen als Attributen flexibler sein. Als ich von der Datei bpython config.py einen Vorsprung hatte, kam ich auf:

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 liest:

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 auf diese Weise verwendet:

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

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

Leckerste Idee überhaupt? Was ist die beste Vorgehensweise, um mit diesen Situationen fertig zu werden? Was ist Ihr Weg, globale Namen und Variablen in Ihrem Paket zu speichern und abzurufen?

70
Rigel Di Scala

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, um gegebenenfalls darauf zu verweisen. Sie können auch zusätzliche Standardwerte aus Ihrem Code übergeben. Es ordnet auch Attribut- und Mapping-Stil-Syntax demselben Konfigurationsobjekt zu. 

5
Keith

Verwenden Sie einfach die integrierten Typen wie folgt:

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

Sie würden auf die Werte wie folgt zugreifen:

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

Wenn Sie bereit sind, das Potenzial zur Berechnung von Ausdrücken in Ihrem Konfigurationsbaum zu opfern, können Sie YAML verwenden und eine lesbarere Konfigurationsdatei wie diese erhalten:

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

und verwenden Sie eine Bibliothek wie PyYAML , um die Konfigurationsdatei auf sinnvolle Weise zu analysieren und darauf zuzugreifen

48
blubb

Ähnlich wie bei blubb's Antwort. Ich empfehle, sie mit Lambda-Funktionen zu erstellen, um 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

Das riecht zwar so, als würden Sie vielleicht eine Klasse machen.

Oder, wie MarkM bemerkt hat, können Sie namedtuple verwenden.

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
8
Cory B

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
5
Husky

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 Nutzung:

if __== "__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

.. sollte dir gefallen, weil:

  • verwendet class variables (kein Objekt zum Weitergeben/kein Singleton erforderlich),
  • verwendet eingekapselte built-in-Typen und sieht aus (ist) ein Methodenaufruf für App
  • hat die Kontrolle über die einzelnen Konfigurationen immutability, mutable Globals sind die schlechtesten Globals.
  • fördert den konventionellen und namentlich genannten Zugriff/Lesbarkeit in Ihrem Quellcode
  • ist eine simple-Klasse, erzwingt jedoch einen strukturierten Zugriff. Alternativ können Sie @property verwenden. Dies erfordert jedoch mehr variablen Verarbeitungscode pro Element und ist objektbasiert.
  • erfordert minimale Änderungen, um neue Konfigurationselemente hinzuzufügen und die Veränderlichkeit festzulegen.

--Edit--: Für große Anwendungen ist das Speichern von Werten in einer YAML-Datei (d. H. Eigenschaften) und das Lesen dieser Daten als unveränderliche Daten ein besserer Ansatz (d. H. blubb/ohaals Antwort ). Für kleine Anwendungen ist diese Lösung einfacher.

5
pds

Eine kleine Variante 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:

#globals.py

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

class runtime :
    debug = False
    output = 'stdio'

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

import globals as gl

Nun kann jeder Code als solcher auf Werte zugreifen und sie einstellen:

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

Leute vergessen, dass Klassen existieren, auch wenn kein Objekt instanziiert wird, das Mitglied dieser Klasse ist. Und Variablen in einer Klasse, denen 'self' nicht vorangestellt ist. werden von allen Instanzen der Klasse gemeinsam genutzt, auch wenn keine vorhanden sind. Sobald "debug" durch einen beliebigen Code geändert wird, wird die Änderung für alle anderen Codes angezeigt. 

Durch den Import als gl können Sie mehrere solcher Dateien und Variablen haben, mit denen Sie auf Codedateien, Funktionen usw. zugreifen und Werte festlegen können, ohne jedoch die Gefahr einer Namensraumkollision. 

Dies hat einige der gescheiten Fehlerprüfungen anderer Ansätze, ist aber einfach und leicht zu verfolgen.

3
eSurfsnake

bitte überprüfen Sie das IPython-Konfigurationssystem, das über Traitlets für die von Ihnen durchgeführte Typüberprüfung implementiert wird.

Hier zugeschnitten und eingefügt, um die SO -Richtlinien einzuhalten, nicht nur Links zu löschen, da sich der Inhalt von Links im Laufe der Zeit ändert.

Traitlets Dokumentation

Hier sind die Hauptanforderungen, die wir an unser Konfigurationssystem stellen wollten:

Unterstützung für hierarchische Konfigurationsinformationen.

Vollständige Integration mit Befehlszeilenoptionsparsern. Sie möchten häufig eine Konfigurationsdatei lesen, überschreiben dann jedoch einige Werte mit Befehlszeilenoptionen. Unser Konfigurationssystem automatisiert diesen Prozess und ermöglicht, dass jede Befehlszeilenoption mit einem bestimmten Attribut in der Konfigurationshierarchie verknüpft wird, das sie überschreibt.

Konfigurationsdateien, die selbst gültiger Python-Code sind. Dies bewirkt viele Dinge. Erstens ist es möglich, eine Logik in Ihre Konfigurationsdateien einzufügen, die Attribute auf der Grundlage Ihres Betriebssystems, der Netzwerkeinrichtung, der Python-Version usw. festlegt. Zweitens verfügt Python über eine extrem einfache Syntax für den Zugriff auf hierarchische Datenstrukturen, nämlich den regulären Zugriff auf Attribute (Foo). Bar.Bam.name). Drittens können Benutzer mithilfe von Python Konfigurationsattribute von einer Konfigurationsdatei in eine andere importieren. Viertens, obwohl Python dynamisch typisiert ist, gibt es Typen, die zur Laufzeit überprüft werden können. Eine 1 in einer Konfigurationsdatei ist also die ganze Zahl '1', während eine '1' eine Zeichenfolge ist.

Eine vollständig automatisierte Methode, um die Konfigurationsinformationen zur Laufzeit an die Klassen zu übergeben, die sie benötigen. Das Schreiben von Code, der eine Konfigurationshierarchie durchläuft, um ein bestimmtes Attribut zu extrahieren, ist schmerzhaft. Wenn Sie über komplexe Konfigurationsinformationen mit Hunderten von Attributen verfügen, möchten Sie diesmal weinen.

Typprüfung und Validierung, bei der die gesamte Konfigurationshierarchie vor der Laufzeit nicht statisch angegeben werden muss. Python ist eine sehr dynamische Sprache und Sie wissen nicht immer alles, was konfiguriert werden muss, wenn ein Programm gestartet wird.

Um dies zu erreichen, definieren sie im Wesentlichen 3 Objektklassen und deren Beziehungen zueinander:

1) Konfiguration - im Grunde ein ChainMap/Basis-Diktier mit einigen Verbesserungen beim 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 Einzelsoftware.

In ihren Worten:

Anwendung: anwendung

Eine Anwendung ist ein Prozess, der eine bestimmte Aufgabe erledigt. Die naheliegendste Anwendung ist das Befehlszeilenprogramm ipython. 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 die Anwendung erstellt. Diese konfigurierbaren Objekte implementieren die eigentliche Logik der Anwendung und wissen, wie sie sich angesichts des Konfigurationsobjekts selbst konfigurieren müssen.

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

Ein konfigurierbares 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 eine Sache.

Dieses Konfigurierbare ist eine Unterklasse von HasTraits, die sich selbst konfigurieren kann. Merkmale der 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.

0
jLi