Cara paling Pythonic untuk menyediakan variabel konfigurasi global di config.py? [Tutup]

100

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?

Rigel Di Scala
sumber
3
Anda sudah membuat keputusan di sini yang mungkin bagus atau mungkin juga tidak. Konfigurasi itu sendiri dapat disimpan dengan berbagai cara, seperti JSON, XML, tata bahasa yang berbeda untuk * nix dan Windows, dan seterusnya. Bergantung pada siapa yang menulis file konfigurasi (alat, manusia, latar belakang apa?) Tata bahasa yang berbeda mungkin lebih disukai. Paling sering mungkin bukan ide yang baik untuk membiarkan file konfigurasi ditulis dalam bahasa yang sama dengan yang Anda gunakan untuk program Anda, karena itu memberikan terlalu banyak kekuatan kepada pengguna (mungkin Anda sendiri, tetapi Anda sendiri mungkin tidak mengingat semua yang bisa salah beberapa bulan ke depan).
erikbwork
4
Seringkali saya akhirnya menulis file konfigurasi JSON. Itu dapat dibaca ke dalam struktur python dengan mudah dan juga dibuat dengan alat. Tampaknya memiliki fleksibilitas paling tinggi dan satu-satunya biaya adalah beberapa kawat gigi yang mungkin mengganggu pengguna. Tapi aku tidak pernah menulis Egg. Mungkin itu cara standar. Kalau begitu abaikan saja komentar saya di atas.
erikbwork
1
Anda dapat menggunakan "vars (self)" daripada "self .__ dict __.
Keys
1
Kemungkinan duplikat dari Apa praktik terbaik menggunakan file pengaturan dengan Python? Mereka menjawab "Banyak cara yang mungkin, dan utas bikeshed sudah ada. Config.py bagus kecuali Anda peduli dengan keamanan."
Nikana Reklawyks
Saya akhirnya menggunakan python-box, lihat jawaban
berevolusi

Jawaban:

5

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.

Keith
sumber
6
The basicconfig.pyberkas dimaksud tampaknya telah pindah ke github.com/kdart/pycopia/blob/master/core/pycopia/...
Paul M Furley
Saya tahu ini berumur beberapa tahun, tapi saya seorang pemula dan saya pikir file konfigurasi ini pada dasarnya adalah apa yang saya cari (mungkin terlalu maju), dan saya ingin memahaminya lebih baik. Apakah saya hanya lulus inisialisasi ConfigHolderdengan dikt konfigurasi yang ingin saya setel dan diteruskan di antara modul?
Jinx
@Jinx Pada titik ini saya akan menggunakan (dan saya sedang menggunakan) file YAML dan PyYAML untuk konfigurasi. Saya juga menggunakan modul pihak ketiga yang disebut confitdan mendukung penggabungan beberapa sumber. Ini bagian dari modul devtest.config baru .
Keith
57

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

blubb
sumber
Tetapi biasanya Anda ingin memiliki file konfigurasi yang berbeda sehingga tidak memiliki data konfigurasi di dalam kode Anda. Jadi ´config´ akan menjadi file JSON / YAML eksternal yang harus Anda muat dari disk setiap kali Anda ingin mengaksesnya, di setiap kelas. Saya yakin pertanyaannya adalah "memuat sekali" dan memiliki akses seperti global ke data yang dimuat. Bagaimana Anda melakukannya dengan solusi yang Anda sarankan?
omni
3
jika hanya ada sesuatu untuk menyimpan data dalam memori ^^
cinatic
16

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:

  • menggunakan variabel kelas (tidak ada objek untuk diedarkan / tidak diperlukan singleton),
  • menggunakan jenis built-in yang dienkapsulasi dan sepertinya (adalah) panggilan metode App,
  • memiliki kendali atas kekekalan konfigurasi individu , global yang dapat berubah adalah jenis global yang terburuk .
  • mempromosikan akses / keterbacaan konvensional dan terkenal dalam kode sumber Anda
  • adalah kelas sederhana tetapi memberlakukan akses terstruktur , alternatif lain yang dapat digunakan @property, tetapi memerlukan lebih banyak kode penanganan variabel per item dan berbasis objek.
  • membutuhkan sedikit perubahan untuk menambahkan item konfigurasi baru dan mengatur mutabilitasnya.

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

pds
sumber
9

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
Serak
sumber
8

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
Cory-G
sumber
3
passadalah nama variabel yang tidak menguntungkan, karena ini juga merupakan kata kunci.
Thomas Schreiter
Oh ya ... Saya baru saja mengumpulkan contoh bodoh ini. Saya akan mengubah nama
Cory-G
Untuk pendekatan semacam ini, Anda dapat mempertimbangkan sebuah kelas daripada mkDictlambda. Jika kita memanggil kelas kita User, kunci kamus "config" Anda akan diinisialisasi seperti {'st3v3': User('password','blonde','Steve Booker')}. Ketika "pengguna" Anda dalam uservariabel, Anda kemudian dapat mengakses propertinya sebagai user.hair, dll.
Andrew Palmer
Jika Anda menyukai gaya ini, Anda juga dapat memilih untuk menggunakan collections.namedtuple . User = namedtuple('User', 'passwd hair name'); config = {'st3v3': User('password', 'blonde', 'Steve Booker')}
MarkM
7

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.

eSurfsnake
sumber
1
Ini salah menamai modul 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 (yaitu DBInfo) dan huruf besar dengan garis bawah untuk apa yang disebut konstanta (yaitu DEBUG).
Nuno André
1
Terima kasih @ NunoAndré atas komentarnya, sampai saya membacanya saya berpikir bahwa jawaban ini melakukan sesuatu yang aneh globals, penulis harus mengubah nama
oglop
Pendekatan ini adalah tujuan saya. Namun saya melihat banyak pendekatan yang dikatakan orang sebagai "yang terbaik". Dapatkah Anda menyatakan beberapa kekurangan dalam menerapkan config.py seperti ini?
Yash Nag
5

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

  • Keadaan berubah global yang tidak terkontrol .
pds
sumber
Ini tidak berguna untuk menggunakan file .ini jika Anda perlu menerapkan pernyataan if di file Anda yang lain untuk mengubah konfigurasi. Akan lebih baik jika menggunakan config.py, tetapi jika nilainya tidak berubah, dan Anda cukup memanggil dan menggunakannya, saya setuju dengan penggunaan file of.ini.
Kourosh
3

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

Berikut adalah persyaratan utama yang kami ingin sistem konfigurasi kami miliki:

Dukungan untuk informasi konfigurasi hierarki.

Integrasi penuh dengan parser opsi baris perintah. Seringkali, Anda ingin membaca file konfigurasi, tetapi kemudian mengganti beberapa nilai dengan opsi baris perintah. Sistem konfigurasi kami mengotomatiskan proses ini dan mengizinkan setiap opsi baris perintah untuk ditautkan ke atribut tertentu dalam hierarki konfigurasi yang akan diganti.

File konfigurasi yang merupakan kode Python yang valid. Ini menyelesaikan banyak hal. Pertama, menjadi mungkin untuk meletakkan logika dalam file konfigurasi Anda yang menetapkan atribut berdasarkan sistem operasi Anda, pengaturan jaringan, versi Python, dll. Kedua, Python memiliki sintaks yang sangat sederhana untuk mengakses struktur data hierarki, yaitu akses atribut biasa (Foo. Bar.Bam.name). Ketiga, menggunakan Python memudahkan pengguna untuk mengimpor atribut konfigurasi dari satu file konfigurasi ke file konfigurasi lainnya. Keempat, meskipun Python diketik secara dinamis, ia memiliki jenis yang dapat diperiksa saat runtime. Jadi, angka 1 dalam file konfigurasi adalah bilangan bulat '1', sedangkan '1' adalah string.

Metode yang sepenuhnya otomatis untuk mendapatkan informasi konfigurasi ke kelas yang membutuhkannya pada waktu proses. Menyakitkan menulis kode yang menjalankan hierarki konfigurasi untuk mengekstrak atribut tertentu. Ketika Anda memiliki informasi konfigurasi yang kompleks dengan ratusan atribut, ini membuat Anda ingin menangis.

Pemeriksaan jenis dan validasi yang tidak mengharuskan seluruh hierarki konfigurasi ditentukan secara statis sebelum runtime. Python adalah bahasa yang sangat dinamis dan Anda tidak selalu tahu semua yang perlu dikonfigurasi saat program dimulai.

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:

Aplikasi: Aplikasi

Aplikasi adalah proses yang melakukan pekerjaan tertentu. Aplikasi yang paling jelas adalah program baris perintah ipython. Setiap aplikasi membaca satu atau beberapa file konfigurasi dan satu set opsi baris perintah, lalu menghasilkan objek konfigurasi master untuk aplikasi tersebut. Objek konfigurasi ini kemudian diteruskan ke objek yang dapat dikonfigurasi yang dibuat aplikasi. Objek yang dapat dikonfigurasi ini mengimplementasikan logika aplikasi yang sebenarnya dan mengetahui cara mengonfigurasi dirinya sendiri berdasarkan objek konfigurasi.

Aplikasi selalu memiliki atribut log yang merupakan Logger yang dikonfigurasi. Ini memungkinkan konfigurasi logging terpusat per aplikasi. Dapat dikonfigurasi: Dapat dikonfigurasi

Configurable adalah kelas Python biasa yang berfungsi sebagai kelas dasar untuk semua kelas utama dalam aplikasi. Kelas dasar yang Dapat Dikonfigurasi ringan dan hanya melakukan satu hal.

Configurable ini adalah subclass dari HasTraits yang tahu cara mengkonfigurasi dirinya sendiri. Ciri tingkat kelas dengan metadata config = True menjadi nilai yang dapat dikonfigurasi dari baris perintah dan file konfigurasi.

Pengembang membuat subkelas yang dapat dikonfigurasi yang menerapkan semua logika dalam aplikasi. Masing-masing subclass ini memiliki informasi konfigurasinya sendiri yang mengontrol bagaimana instance dibuat.

jLi
sumber