W moich niekończących się poszukiwaniach zbyt skomplikowanych prostych rzeczy, badam najbardziej „Pythonowy” sposób dostarczania globalnych zmiennych konfiguracyjnych w typowym pliku „ config.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?
źródło
python-box
, zobacz tę odpowiedźOdpowiedzi:
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.
źródło
basicconfig.py
Plik mowa wydaje się, że przeniósł się do github.com/kdart/pycopia/blob/master/core/pycopia/...ConfigHolder
z zapisem konfiguracji, które chcę ustawić i przekazywać między modułami?confit
i obsługuje on łączenie wielu źródeł. Jest częścią nowego modułu devtest.config .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
źródło
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ż:
App
,@property
, ale wymaga to więcej kodu obsługi zmiennych na element i jest oparta na obiektach.--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.
źródło
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
źródło
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
źródło
pass
jest niefortunną nazwą zmiennej, ponieważ jest również słowem kluczowym.mkDict
lambdy. 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 wuser
zmiennej, można wtedy uzyskać dostęp do jego właściwości jakouser.hair
itpUser = namedtuple('User', 'passwd hair name'); config = {'st3v3': User('password', 'blonde', 'Steve Booker')}
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.
źródło
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 (tjDEBUG
.).globals
, autor powinien zmienić nazwę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:
źródło
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
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:
źródło