Większość Pythonowych sposobów dostarczania globalnych zmiennych konfiguracyjnych w config.py? [Zamknięte]

99

W moich niekończących się poszukiwaniach zbyt skomplikowanych prostych rzeczy, badam najbardziej „Pythonowy” sposób dostarczania globalnych zmiennych konfiguracyjnych w typowym plikuconfig.py ”, który można znaleźć w pakietach Python egg.

Tradycyjny sposób (aaa, dobry stary #define !) Wygląda następująco:

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

Dlatego zmienne globalne są importowane w jeden z następujących sposobów:

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

lub:

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

Ma to sens, ale czasami może być trochę nieuporządkowane, zwłaszcza gdy próbujesz zapamiętać nazwy pewnych zmiennych. Poza tym dostarczenie obiektu „konfiguracyjnego” ze zmiennymi jako atrybutami może być bardziej elastyczne. Tak więc, kierując się plikiem bpython config.py, wymyśliłem:

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

i „config.py”, który importuje klasę i czyta w następujący sposób:

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'

i jest używany w ten sposób:

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

Co wydaje się bardziej czytelnym, wyrazistym i elastycznym sposobem przechowywania i pobierania zmiennych globalnych wewnątrz pakietu.

Najmodniejszy pomysł na świecie? Jaka jest najlepsza praktyka radzenia sobie w takich sytuacjach? Jaki jest twój sposób przechowywania i pobierania globalnych nazw i zmiennych wewnątrz pakietu?

Rigel Di Scala
źródło
3
Podjąłeś już tutaj decyzję, która może być dobra lub nie. Samą konfigurację można przechowywać na różne sposoby, na przykład JSON, XML, różne gramatyki dla * nixes i Windows i tak dalej. W zależności od tego, kto pisze plik konfiguracyjny (narzędzie, człowiek, jakie tło?) Mogą być preferowane różne gramatyki. Najczęściej nie jest dobrym pomysłem pozostawienie pliku konfiguracyjnego napisanego w tym samym języku, którego używasz w swoim programie, ponieważ daje to zbyt duże możliwości użytkownikowi (kim możesz być Ty, ale Ty sam możesz nie pamiętać popełnić błąd kilka miesięcy do przodu).
erikbwork
4
Często piszę plik konfiguracyjny JSON. Można go łatwo wczytać do struktur Pythona, a także utworzyć za pomocą narzędzia. Wydaje się, że ma największą elastyczność, a jedyny koszt to kilka aparatów ortodontycznych, które mogą być denerwujące dla użytkownika. Jednak nigdy nie napisałem Jajka. Może to jest standardowy sposób. W takim razie zignoruj ​​mój komentarz powyżej.
erikbwork
1
Możesz użyć „vars (self)” zamiast „self .__ dict __.
Keys
1
Możliwy duplikat tego, jakie są najlepsze praktyki przy użyciu pliku ustawień w Pythonie? Odpowiadają: „Możliwych jest wiele sposobów, a wątek przerobiony na rowery już istnieje. Config.py jest dobry, chyba że zależy Ci na bezpieczeństwie”.
Nikana Reklawyks
Skończyło się na używaniu python-box, zobacz tę odpowiedź
ewoluował

Odpowiedzi:

5

Raz to zrobiłem. Ostatecznie uznałem, że mój uproszczony basicconfig.py jest odpowiedni dla moich potrzeb. Możesz przekazać przestrzeń nazw z innymi obiektami, aby mogła się do niej odwoływać, jeśli zajdzie taka potrzeba. Możesz również przekazać dodatkowe wartości domyślne z kodu. Mapuje również składnię atrybutów i stylu odwzorowania na ten sam obiekt konfiguracyjny.

Keith
źródło
6
basicconfig.pyPlik mowa wydaje się, że przeniósł się do github.com/kdart/pycopia/blob/master/core/pycopia/...
Paul M Furley
Wiem, że to ma kilka lat, ale jestem początkującym i myślę, że ten plik konfiguracyjny jest zasadniczo tym, czego szukam (może zbyt zaawansowany) i chciałbym to lepiej zrozumieć. Czy po prostu przekazuję inicjalizację ConfigHolderz zapisem konfiguracji, które chcę ustawić i przekazywać między modułami?
Jinx
@Jinx W tym momencie użyłbym (i obecnie używam) pliku YAML i PyYAML do konfiguracji. Używam również modułu innej firmy o nazwie confiti obsługuje on łączenie wielu źródeł. Jest częścią nowego modułu devtest.config .
Keith,
57

Co powiesz na używanie tylko typów wbudowanych, takich jak ten:

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

Dostęp do wartości uzyskasz w następujący sposób:

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

Jeśli chcesz poświęcić potencjał obliczania wyrażeń w swoim drzewie konfiguracji, możesz użyć YAML i otrzymać bardziej czytelny plik konfiguracyjny, taki jak ten:

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

i użyj biblioteki takiej jak PyYAML, aby w wygodny sposób przeanalizować i uzyskać dostęp do pliku konfiguracyjnego

blubb
źródło
Ale zwykle chcesz mieć różne pliki konfiguracyjne, a zatem nie masz żadnych danych konfiguracyjnych w kodzie. Zatem „config” byłby zewnętrznym plikiem JSON / YAML, który musisz ładować z dysku za każdym razem, gdy chcesz uzyskać do niego dostęp, w każdej klasie. Uważam, że chodzi o to, aby „załadować raz” i mieć globalny dostęp do załadowanych danych. Jak byś to zrobił z zaproponowanym przez Ciebie rozwiązaniem?
omni
3
gdyby tylko istniało coś do przechowywania danych w pamięci ^^
cinatic
16

Podoba mi się to rozwiązanie do małych aplikacji :

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

A następnie użycie to:

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

.. powinieneś go polubić, ponieważ:

  • używa zmiennych klasowych (nie ma obiektu do przekazania / nie jest wymagany singleton),
  • używa hermetyzowanych typów wbudowanych i wygląda jak (jest) wywołaniem metody App,
  • ma kontrolę nad niezmiennością poszczególnych konfiguracji , zmienne globalne są najgorszym rodzajem globalnych .
  • promuje konwencjonalny i dobrze nazwany dostęp / czytelność w kodzie źródłowym
  • jest prostą klasą, ale wymusza dostęp strukturalny , alternatywą jest użycie @property, ale wymaga to więcej kodu obsługi zmiennych na element i jest oparta na obiektach.
  • wymaga minimalnych zmian, aby dodać nowe elementy konfiguracji i ustawić jej zmienność.

--Edytuj-- : W przypadku dużych aplikacji przechowywanie wartości w pliku YAML (tj. Właściwości) i odczytywanie ich jako niezmiennych danych jest lepszym podejściem (np . Odpowiedź blubb / ohaal ). W przypadku małych aplikacji powyższe rozwiązanie jest prostsze.

pds
źródło
9

A co z klasami?

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

# main.py
from config import MYSQL

print(MYSQL.PORT) # 3306
Ochrypły
źródło
8

Podobnie jak odpowiedź Blubba. Proponuję zbudować je z funkcjami lambda, aby zredukować kod. Lubię to:

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

Ale to pachnie tak, jakbyś chciał zrobić klasę.

Lub, jak zauważył MarkM, możesz użyć 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
źródło
3
passjest niefortunną nazwą zmiennej, ponieważ jest również słowem kluczowym.
Thomas Schreiter
O tak ... Właśnie zebrałem ten głupi przykład. Zmienię nazwę
Cory-G
W przypadku tego rodzaju podejścia można rozważyć klasę zamiast mkDictlambdy. Gdybyśmy wywołali naszą klasę User, klucze słownika "config" zostałyby zainicjalizowane w podobny sposób {'st3v3': User('password','blonde','Steve Booker')}. Gdy „użytkownik” jest w userzmiennej, można wtedy uzyskać dostęp do jego właściwości jako user.hairitp
Andrew Palmer
Jeśli podoba Ci się ten styl, możesz również zdecydować się na użycie collections.namedtuple . User = namedtuple('User', 'passwd hair name'); config = {'st3v3': User('password', 'blonde', 'Steve Booker')}
MarkM
7

Mała wariacja na temat pomysłu Husky, którego używam. Utwórz plik o nazwie „globals” (lub cokolwiek chcesz), a następnie zdefiniuj w nim wiele klas, takich jak:

#globals.py

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

class runtime :
    debug = False
    output = 'stdio'

Następnie, jeśli masz dwa pliki kodu c1.py i c2.py, oba mogą znajdować się na górze

import globals as gl

Teraz cały kod może uzyskać dostęp i ustawić wartości, takie jak:

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

Ludzie zapominają o istnieniu klas, nawet jeśli nigdy nie utworzono instancji obiektu należącego do tej klasy. Oraz zmienne w klasie, które nie są poprzedzone wyrażeniem „ja”. są współużytkowane we wszystkich instancjach klasy, nawet jeśli ich nie ma. Gdy kod „debugowania” zostanie zmieniony przez dowolny kod, wszystkie inne kody zobaczą zmianę.

Importując go jako gl, możesz mieć wiele takich plików i zmiennych, które umożliwiają dostęp i ustawianie wartości w plikach kodu, funkcjach itp., Ale bez ryzyka kolizji przestrzeni nazw.

Brakuje tu niektórych sprytnych sprawdzeń błędów innych podejść, ale jest proste i łatwe do naśladowania.

eSurfsnake
źródło
1
Niewłaściwe jest nazwanie modułu globals, ponieważ jest to funkcja wbudowana, która zwraca dict z każdym symbolem w bieżącym zasięgu globalnym. Ponadto PEP8 zaleca CamelCase (ze wszystkimi dużymi literami w akronimach) dla klas (tj. DBInfo) Oraz wielkie litery z podkreśleniem dla tak zwanych stałych (tj DEBUG.).
Nuno André
1
Dzięki @ NunoAndré za komentarz, dopóki go nie przeczytałem, myślałem, że ta odpowiedź robi coś dziwnego globals, autor powinien zmienić nazwę
oglop
To podejście jest moim celem. Jednak widzę wiele podejść, które ludzie mówią, że są „najlepsze”. Czy możesz opisać pewne niedociągnięcia w implementacji config.py?
Yash Nag
5

Bądźmy szczerzy, prawdopodobnie powinniśmy rozważyć użycie biblioteki obsługiwanej przez Python Software Foundation :

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

Przykład konfiguracji: (format ini, ale dostępny JSON)

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

[bitbucket.org]
User = hg

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

Przykład kodu:

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

Zapewnienie globalnej dostępności:

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

Wady:

  • Niekontrolowany globalny zmienny stan.
pds
źródło
Używanie pliku .ini nie jest przydatne, jeśli musisz zastosować instrukcje if w innych plikach, aby zmienić konfigurację. Lepiej byłoby użyć zamiast tego config.py, ale jeśli wartości się nie zmieniają, a ty po prostu go wywołujesz i używasz, zgadzam się na użycie pliku of.ini.
Kourosh,
3

sprawdź system konfiguracji IPython, zaimplementowany za pomocą traitlets dla wymuszania typów, które wykonujesz ręcznie.

Wytnij i wklej tutaj, aby zachować zgodność z wytycznymi SO dotyczącymi nie tylko upuszczania linków, ponieważ zawartość łączy zmienia się w czasie.

dokumentacja cech

Oto główne wymagania, jakie powinien spełniać nasz system konfiguracyjny:

Obsługa hierarchicznych informacji konfiguracyjnych.

Pełna integracja z parserami opcji wiersza poleceń. Często chcesz odczytać plik konfiguracyjny, ale potem nadpisać niektóre wartości opcjami wiersza poleceń. Nasz system konfiguracji automatyzuje ten proces i umożliwia powiązanie każdej opcji wiersza poleceń z określonym atrybutem w hierarchii konfiguracji, który ma nadpisać.

Pliki konfiguracyjne, które same są prawidłowym kodem w Pythonie. To pozwala na wiele rzeczy. Po pierwsze, możliwe staje się umieszczenie logiki w twoich plikach konfiguracyjnych, która ustawia atrybuty w oparciu o twój system operacyjny, konfigurację sieci, wersję Pythona itp. Po drugie, Python ma super prostą składnię dostępu do hierarchicznych struktur danych, a mianowicie regularny dostęp do atrybutów (Foo. Bar.Bam.name). Po trzecie, używanie języka Python ułatwia użytkownikom importowanie atrybutów konfiguracji z jednego pliku konfiguracyjnego do drugiego. Po czwarte, mimo że Python jest typowany dynamicznie, ma typy, które można sprawdzić w czasie wykonywania. Zatem 1 w pliku konfiguracyjnym to liczba całkowita „1”, a „1” to łańcuch.

W pełni zautomatyzowana metoda pobierania informacji konfiguracyjnych do klas, które ich potrzebują w czasie wykonywania. Pisanie kodu poruszającego się po hierarchii konfiguracji w celu wyodrębnienia określonego atrybutu jest bolesne. Kiedy masz złożone informacje konfiguracyjne z setkami atrybutów, chcesz płakać.

Sprawdzanie typów i walidacja, które nie wymagają statycznego określenia całej hierarchii konfiguracji przed uruchomieniem. Python to bardzo dynamiczny język i nie zawsze wiesz wszystko, co należy skonfigurować podczas uruchamiania programu.

Aby to osiągnąć, definiują w zasadzie 3 klasy obiektów i ich wzajemne relacje:

1) Konfiguracja - w zasadzie ChainMap / basic dict z kilkoma ulepszeniami do scalania.

2) Configurable - klasa bazowa do podklasy wszystkich rzeczy, które chcesz skonfigurować.

3) Aplikacja - obiekt, którego instancja jest tworzona w celu wykonania określonej funkcji aplikacji lub Twoja główna aplikacja dla oprogramowania jednofunkcyjnego.

W ich słowach:

Zastosowanie: aplikacja

Aplikacja to proces, który wykonuje określoną pracę. Najbardziej oczywistą aplikacją jest program wiersza poleceń ipython. Każda aplikacja odczytuje jeden lub więcej plików konfiguracyjnych i pojedynczy zestaw opcji wiersza poleceń, a następnie tworzy główny obiekt konfiguracyjny dla aplikacji. Ten obiekt konfiguracyjny jest następnie przekazywany do konfigurowalnych obiektów tworzonych przez aplikację. Te konfigurowalne obiekty implementują rzeczywistą logikę aplikacji i wiedzą, jak się konfigurować, biorąc pod uwagę obiekt konfiguracyjny.

Aplikacje zawsze mają atrybut dziennika, który jest skonfigurowanym rejestratorem. Pozwala to na scentralizowaną konfigurację rejestrowania dla każdej aplikacji. Configurable: Configurable

Konfigurowalna to zwykła klasa Pythona, która służy jako klasa bazowa dla wszystkich głównych klas w aplikacji. Klasa bazowa Configurable jest lekka i robi tylko jedną rzecz.

Ten Configurable jest podklasą HasTraits, która wie, jak się skonfigurować. Cechy poziomu klasy z metadanymi config = True stają się wartościami, które można skonfigurować z wiersza poleceń i plików konfiguracyjnych.

Programiści tworzą konfigurowalne podklasy, które implementują całą logikę w aplikacji. Każda z tych podklas ma własne informacje o konfiguracji, które kontrolują sposób tworzenia instancji.

jLi
źródło