Dalam pencarian tanpa akhir saya dalam hal-hal sederhana yang terlalu rumit, saya meneliti cara yang paling 'Pythonic' untuk menyediakan variabel konfigurasi global di dalam ' config.py ' khas yang ditemukan dalam paket telur Python.
Cara tradisional (aah, good ol ' #define !) Adalah sebagai berikut:
MYSQL_PORT = 3306
MYSQL_DATABASE = 'mydb'
MYSQL_DATABASE_TABLES = ['tb_users', 'tb_groups']
Oleh karena itu, variabel global diimpor dengan salah satu cara berikut:
from config import *
dbname = MYSQL_DATABASE
for table in MYSQL_DATABASE_TABLES:
print table
atau:
import config
dbname = config.MYSQL_DATABASE
assert(isinstance(config.MYSQL_PORT, int))
Masuk akal, tetapi terkadang bisa sedikit berantakan, terutama saat Anda mencoba mengingat nama variabel tertentu. Selain itu, menyediakan objek 'konfigurasi' , dengan variabel sebagai atribut , mungkin lebih fleksibel. Jadi, mengambil petunjuk dari file bpython config.py, saya datang dengan:
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)])
dan 'config.py' yang mengimpor kelas dan berbunyi sebagai berikut:
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'
dan digunakan dengan cara ini:
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))
Yang tampaknya merupakan cara yang lebih mudah dibaca, ekspresif, dan fleksibel dalam menyimpan dan mengambil variabel global di dalam sebuah paket.
Ide terlama yang pernah ada? Apa praktik terbaik untuk menghadapi situasi ini? Apa cara Anda menyimpan dan mengambil nama dan variabel global di dalam paket Anda?
sumber
python-box
, lihat jawabanJawaban:
Saya melakukan itu sekali. Akhirnya saya menemukan basicconfig.py saya yang disederhanakan cukup untuk kebutuhan saya. Anda bisa meneruskan namespace dengan objek lain untuk referensi jika perlu. Anda juga dapat mengirimkan default tambahan dari kode Anda. Itu juga memetakan atribut dan sintaks gaya pemetaan ke objek konfigurasi yang sama.
sumber
basicconfig.py
berkas dimaksud tampaknya telah pindah ke github.com/kdart/pycopia/blob/master/core/pycopia/...ConfigHolder
dengan dikt konfigurasi yang ingin saya setel dan diteruskan di antara modul?confit
dan mendukung penggabungan beberapa sumber. Ini bagian dari modul devtest.config baru .Bagaimana kalau hanya menggunakan tipe bawaan seperti ini:
config = { "mysql": { "user": "root", "pass": "secret", "tables": { "users": "tb_users" } # etc } }
Anda akan mengakses nilai-nilai sebagai berikut:
config["mysql"]["tables"]["users"]
Jika Anda bersedia mengorbankan potensi untuk menghitung ekspresi di dalam pohon konfigurasi Anda, Anda dapat menggunakan YAML dan berakhir dengan file konfigurasi yang lebih mudah dibaca seperti ini:
mysql: - user: root - pass: secret - tables: - users: tb_users
dan menggunakan pustaka seperti PyYAML untuk mengurai dan mengakses file konfigurasi secara praktis
sumber
Saya suka solusi ini untuk aplikasi kecil :
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")
Dan kemudian penggunaannya adalah:
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
.. Anda harus menyukainya karena:
App
,@property
, tetapi memerlukan lebih banyak kode penanganan variabel per item dan berbasis objek.--Edit-- : Untuk aplikasi besar, menyimpan nilai dalam file YAML (yaitu properti) dan membacanya sebagai data yang tidak dapat diubah adalah pendekatan yang lebih baik (yaitu jawaban blubb / ohaal ). Untuk aplikasi kecil, solusi di atas lebih sederhana.
sumber
Bagaimana kalau menggunakan kelas?
# config.py class MYSQL: PORT = 3306 DATABASE = 'mydb' DATABASE_TABLES = ['tb_users', 'tb_groups'] # main.py from config import MYSQL print(MYSQL.PORT) # 3306
sumber
Mirip dengan jawaban blubb. Saya sarankan untuk membuatnya dengan fungsi lambda untuk mengurangi kode. Seperti ini:
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
Ini memang berbau seperti Anda mungkin ingin membuat kelas.
Atau, seperti yang dicatat MarkM, Anda bisa menggunakan
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
sumber
pass
adalah nama variabel yang tidak menguntungkan, karena ini juga merupakan kata kunci.mkDict
lambda. Jika kita memanggil kelas kitaUser
, kunci kamus "config" Anda akan diinisialisasi seperti{'st3v3': User('password','blonde','Steve Booker')}
. Ketika "pengguna" Anda dalamuser
variabel, Anda kemudian dapat mengakses propertinya sebagaiuser.hair
, dll.User = namedtuple('User', 'passwd hair name'); config = {'st3v3': User('password', 'blonde', 'Steve Booker')}
Variasi kecil dari ide Husky yang saya gunakan. Buat file bernama 'global' (atau apa pun yang Anda suka) dan kemudian tentukan beberapa kelas di dalamnya, seperti:
#globals.py class dbinfo : # for database globals username = 'abcd' password = 'xyz' class runtime : debug = False output = 'stdio'
Kemudian, jika Anda memiliki dua file kode c1.py dan c2.py, keduanya bisa ada di bagian atas
import globals as gl
Sekarang semua kode dapat mengakses dan menetapkan nilai, seperti:
gl.runtime.debug = False print(gl.dbinfo.username)
Orang lupa bahwa kelas ada, meskipun tidak ada objek yang pernah dipakai yang merupakan anggota kelas itu. Dan variabel di kelas yang tidak diawali dengan 'diri'. dibagikan di semua instance kelas, meskipun tidak ada. Setelah 'debug' diubah oleh kode apa pun, semua kode lain melihat perubahan tersebut.
Dengan mengimpornya sebagai gl, Anda dapat memiliki beberapa file dan variabel yang memungkinkan Anda mengakses dan menetapkan nilai di seluruh file kode, fungsi, dll., Tetapi tanpa bahaya tabrakan namespace.
Ini kekurangan beberapa pemeriksaan kesalahan pintar dari pendekatan lain, tetapi sederhana dan mudah diikuti.
sumber
globals
, karena ini adalah fungsi bawaan yang mengembalikan dikt dengan setiap simbol dalam lingkup global saat ini. Selain itu, PEP8 merekomendasikan CamelCase (dengan semua huruf kapital dalam akronim) untuk kelas (yaituDBInfo
) dan huruf besar dengan garis bawah untuk apa yang disebut konstanta (yaituDEBUG
).globals
, penulis harus mengubah namaJujur saja, kita mungkin harus mempertimbangkan untuk menggunakan pustaka yang dikelola Python Software Foundation :
https://docs.python.org/3/library/configparser.html
Contoh konfigurasi: (format ini, tetapi JSON tersedia)
[DEFAULT] ServerAliveInterval = 45 Compression = yes CompressionLevel = 9 ForwardX11 = yes [bitbucket.org] User = hg [topsecret.server.com] Port = 50022 ForwardX11 = no
Contoh kode:
>>> import configparser >>> config = configparser.ConfigParser() >>> config.read('example.ini') >>> config['DEFAULT']['Compression'] 'yes' >>> config['DEFAULT'].getboolean('MyCompression', fallback=True) # get_or_else
Menjadikannya dapat diakses secara global:
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) ....
Kerugian:
sumber
silakan periksa sistem konfigurasi IPython, diimplementasikan melalui traitlets untuk penegakan jenis yang Anda lakukan secara manual.
Potong dan tempel di sini untuk mematuhi pedoman SO untuk tidak hanya membuang tautan karena konten tautan berubah seiring waktu.
dokumentasi sifat
Untuk mencapai ini mereka pada dasarnya mendefinisikan 3 kelas objek dan hubungannya satu sama lain:
1) Konfigurasi - pada dasarnya merupakan ChainMap / perintah dasar dengan beberapa peningkatan untuk penggabungan.
2) Dapat dikonfigurasi - kelas dasar untuk membuat subkelas semua hal yang ingin Anda konfigurasi.
3) Aplikasi - objek yang dipakai untuk menjalankan fungsi aplikasi tertentu, atau aplikasi utama Anda untuk perangkat lunak tujuan tunggal.
Dalam kata-kata mereka:
sumber