Config.py'de global konfigürasyon değişkenleri sağlamanın en Pythonic yolu? [kapalı]


100

Aşırı karmaşık basit şeyler konusundaki sonsuz arayışımda , Python yumurta paketlerinde bulunan tipik ' config.py ' içinde global yapılandırma değişkenleri sağlamak için en 'Pythonic' yolunu araştırıyorum .

Geleneksel yöntem (aah, good ol ' #define !) Aşağıdaki gibidir:

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

Bu nedenle global değişkenler aşağıdaki yollardan biriyle içe aktarılır:

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

veya:

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

Bu mantıklı, ancak bazen biraz dağınık olabilir, özellikle belirli değişkenlerin adlarını hatırlamaya çalıştığınızda. Bunun yanı sıra, bir sağlama 'yapılandırma' nesne ile nitelikler gibi değişkenler , daha esnek olabilir. Böylece, bpython config.py dosyasından bir ipucu alarak şunu buldum :

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

ve sınıfı içe aktaran ve aşağıdaki gibi okuyan bir 'config.py':

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'

ve şu şekilde kullanılır:

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

Bu, bir paket içindeki global değişkenleri depolamanın ve getirmenin daha okunaklı, anlamlı ve esnek bir yolu gibi görünüyor.

Hiç iğrenç bir fikir mi? Bu durumlarla başa çıkmak için en iyi uygulama nedir? Nedir sizin depolamak ve küresel isimleri ve değişkenleri paketinize getiriliyor yolu?


3
Zaten burada iyi olabilecek veya olmayabilecek bir karar vermişsiniz. Yapılandırmanın kendisi, JSON, XML, * nix'ler ve Windows için farklı gramerler ve benzeri gibi farklı şekillerde depolanabilir. Yapılandırma dosyasını kimin yazdığına bağlı olarak (bir araç, bir insan, hangi arka plan?) Farklı gramerler tercih edilebilir. Çoğu zaman, yapılandırma dosyasının programınız için kullandığınız aynı dilde yazılmasına izin vermek iyi bir fikir olmayabilir, çünkü kullanıcıya çok fazla güç verir (kendiniz ne olabilir, ancak kendiniz yapabilecek her şeyi hatırlamayabilirsiniz. birkaç ay önce yanlış gidin).
erikbwork

4
Genellikle bir JSON yapılandırma dosyası yazıyorum. Python yapılarına kolayca okunabilir ve ayrıca bir araçla oluşturulabilir. En fazla esnekliğe sahip gibi görünüyor ve tek maliyet, kullanıcıyı rahatsız edebilecek bazı diş telleri. Yine de hiç Yumurta yazmadım. Belki standart yol budur. Bu durumda yukarıdaki yorumumu görmezden gelin.
erikbwork

1
"Self .__ dict __.
Keys

1
Olası yinelenen Python bir ayar dosyası kullanarak en iyi yöntem nedir? "Pek çok yol mümkündür ve bisiklet bağlantılı bir iş parçacığı zaten mevcuttur. Güvenlik konusunu önemsemediğiniz sürece config.py iyidir."
Nikana Reklawyks

Ben kullanarak sona erdi python-boxgörüyoruz, cevabı
gelişti

Yanıtlar:


5

Bunu bir kez yaptım. Sonunda basitleştirilmiş basicconfig.py'mi ihtiyaçlarım için yeterli buldum . Gerekirse başvurmak için diğer nesnelerle birlikte bir ad alanını iletebilirsiniz. Ayrıca kodunuzdan ek varsayılanlar da geçirebilirsiniz. Ayrıca, nitelik ve eşleme stili sözdizimini aynı yapılandırma nesnesiyle eşler.



Bunun birkaç yaşında olduğunu biliyorum, ama ben bir acemiyim ve bu yapılandırma dosyasının aslında aradığım şey olduğunu düşünüyorum (belki çok gelişmiş) ve daha iyi anlamak istiyorum. ConfigHolderAyarlamak ve modüller arasında geçirmek istediğim bir yapılandırma diktesi ile başlatmaya mı geçmeliyim?
Jinx

@Jinx Bu noktada konfigürasyon için bir YAML dosyası ve PyYAML kullanacağım (ve şu anda kullanıyorum). Ayrıca adı verilen üçüncü taraf bir modül kullanıyorum confitve çoklu kaynağı birleştirmeyi destekliyor. Yeni devtest.config modülünün bir parçasıdır .
Keith

57

Bunun gibi yerleşik türleri kullanmaya ne dersiniz:

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

Değerlere aşağıdaki şekilde erişirsiniz:

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

Yapılandırma ağacınızın içindeki ifadeleri hesaplama potansiyelini feda etmeye istekliyseniz, YAML kullanabilir ve bunun gibi daha okunabilir bir yapılandırma dosyası elde edebilirsiniz:

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

ve yapılandırma dosyasını uygun bir şekilde ayrıştırıp erişmek için PyYAML gibi bir kitaplık kullanın


Ancak normalde farklı yapılandırma dosyalarına sahip olmak ve bu nedenle kodunuzun içinde herhangi bir yapılandırma verisine sahip olmak istemezsiniz. Yani "konfigürasyon", her sınıfta her erişmek istediğinizde diskten yüklemeniz gereken harici bir JSON / YAML dosyası olacaktır. Sorunun "bir kez yüklemek" ve yüklenen verilere küresel benzeri erişime sahip olmak olduğuna inanıyorum. Bunu önerdiğiniz çözümle nasıl yaparsınız?
omni

3
veriyi hafızada tutacak bir şey olsaydı ^^
sinematik

16

Küçük uygulamalar için bu çözümü seviyorum :

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

Ve sonra kullanım:

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

.. beğenmelisiniz çünkü:

  • sınıf değişkenleri kullanır (iletilecek nesne yok / tekil gerekli değil)
  • kapsüllenmiş yerleşik türleri kullanır ve bir yöntem çağrısı gibi görünür App,
  • bireysel yapılandırma değişmezliği üzerinde kontrole sahiptir , değişken küreseller en kötü küresel türlerdir .
  • Kaynak kodunuzda geleneksel ve iyi adlandırılmış erişimi / okunabilirliği teşvik eder
  • Bir edilmektedir basit sınıf ama onu yürütmelidir erişimi yapılandırılmış , alternatif kullanmaktır @property, ama bu ürün başına daha değişken işleme kodu gerektirir ve nesne tabanlı.
  • yeni yapılandırma öğeleri eklemek ve değişkenliğini ayarlamak için minimum değişiklik gerektirir .

--Düzenle-- : Büyük uygulamalar için, değerleri bir YAML (yani özellikler) dosyasında saklamak ve bunu değişmez veriler olarak okumak daha iyi bir yaklaşımdır (yani blubb / ohaal'ın cevabı ). Küçük uygulamalar için yukarıdaki bu çözüm daha basittir.


9

Dersleri kullanmaya ne dersiniz?

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

# main.py
from config import MYSQL

print(MYSQL.PORT) # 3306

8

Blubb'un cevabına benzer. Kodu azaltmak için bunları lambda işlevleriyle oluşturmanızı öneririm. Bunun gibi:

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

Yine de bu, bir ders yapmak isteyebileceğiniz gibi kokuyor.

Veya MarkM'nin de belirttiği gibi kullanabilirsiniz 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

3
passaynı zamanda bir anahtar kelime olduğu için talihsiz bir değişken adıdır.
Thomas Schreiter

Oh evet ... Bu aptal örneği bir araya getirdim. İsmi değiştireceğim
Cory-G

Bu tür bir yaklaşım için, mkDictlambda yerine bir sınıf düşünebilirsiniz . Sınıfımızı Userararsak, "yapılandırma" sözlük anahtarlarınız gibi bir şey başlatılır {'st3v3': User('password','blonde','Steve Booker')}. "Kullanıcınız" bir userdeğişkendeyken, özelliklerine user.hairvb. Olarak erişebilirsiniz .
Andrew Palmer

Bu stili beğendiyseniz, collections.namedtuple kullanmayı da tercih edebilirsiniz . User = namedtuple('User', 'passwd hair name'); config = {'st3v3': User('password', 'blonde', 'Steve Booker')}
MarkM

7

Husky'nin kullandığım fikrinin küçük bir varyasyonu. 'Globals' (veya ne istersen) adında bir dosya oluşturun ve ardından aşağıdaki gibi birden çok sınıf tanımlayın:

#globals.py

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

class runtime :
    debug = False
    output = 'stdio'

Daha sonra, c1.py ve c2.py olmak üzere iki kod dosyanız varsa, her ikisi de üstte olabilir

import globals as gl

Artık tüm kodlar aşağıdaki gibi değerlere erişebilir ve ayarlayabilir:

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

İnsanlar, o sınıfın üyesi olan hiçbir nesne somutlaştırılmasa bile sınıfların var olduğunu unutur. Ve bir sınıftaki 'kendinin' önünde olmayan değişkenler. hiç olmasa bile sınıfın tüm örnekleri arasında paylaşılır. 'Hata ayıklama' herhangi bir kodla değiştirildiğinde, diğer tüm kodlar değişikliği görür.

Bunu gl olarak içe aktararak, kod dosyaları, işlevler vb. Arasında değerlere erişmenize ve bunları ayarlamanıza olanak tanıyan, ancak ad alanı çakışması tehlikesi olmayan birden çok dosya ve değişkene sahip olabilirsiniz.

Bu, diğer yaklaşımların bazı akıllı hata kontrollerinden yoksundur, ancak basit ve takip edilmesi kolaydır.


1
globalsMevcut genel kapsamdaki her sembolle bir dikt döndüren yerleşik bir işlev olduğu için bir modülü adlandırmak yanlış bir tavsiye . Ek olarak, PEP8 sınıflar (yani DBInfo) için CamelCase'i (kısaltmalarla tüm büyük harflerle ) ve sabitler (yani DEBUG) için alt çizgilerle birlikte büyük harfleri önerir .
Nuno André

1
Yorum için teşekkürler @ NunoAndré, okuyana kadar bu cevabın garip bir şey yaptığını düşünüyordum globals, yazar adı değiştirmeli
oglop

Bu yaklaşım benim için. Bununla birlikte, insanların "en iyisi" olduğunu söylediği birçok yaklaşım görüyorum. Config.py'yi bu şekilde uygulamanın bazı eksikliklerinden bahsedebilir misiniz?
Yash Nag

5

Dürüst olalım, muhtemelen Python Software Foundation tarafından yürütülen bir kitaplık kullanmayı düşünmeliyiz :

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

Yapılandırma örneği: (ini biçiminde, ancak JSON mevcuttur)

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

[bitbucket.org]
User = hg

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

Kod örneği:

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

Küresel olarak erişilebilir hale getirmek:

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

Dezavantajlar:

  • Kontrolsüz küresel değiştirilebilir durum.

Yapılandırmayı değiştirmek için diğer dosyalarınızda if ifadeleri uygulamanız gerekiyorsa .ini dosyasını kullanmak kullanışlı değildir. Bunun yerine config.py kullanmak daha iyi olur, ancak değerler değişmezse ve siz onu çağırır ve kullanırsanız ,.ini dosyasının kullanımına katılıyorum.
Kourosh

3

lütfen manuel olarak yaptığınız tür zorlaması için traitlet'ler aracılığıyla uygulanan IPython yapılandırma sistemine bakın.

Zamanla bağlantıların içeriği değiştikçe, yalnızca bağlantıları bırakmamak için SO yönergelerine uymak için buraya kesip yapıştırın.

özellikli belgeler

Yapılandırma sistemimizin sahip olmasını istediğimiz ana gereksinimler şunlardır:

Hiyerarşik yapılandırma bilgileri desteği.

Komut satırı seçeneği ayrıştırıcılarıyla tam entegrasyon. Genellikle, bir yapılandırma dosyasını okumak, ancak daha sonra komut satırı seçenekleriyle bazı değerleri geçersiz kılmak istersiniz. Yapılandırma sistemimiz bu süreci otomatikleştirir ve her komut satırı seçeneğinin, geçersiz kılacak yapılandırma hiyerarşisindeki belirli bir özniteliğe bağlanmasına izin verir.

Kendileri geçerli Python kodu olan yapılandırma dosyaları. Bu pek çok şeyi başarır. İlk olarak, yapılandırma dosyalarınıza işletim sisteminize, ağ kurulumunuza, Python sürümünüze vb. Göre öznitelikleri ayarlayan mantığı koymak mümkün hale gelir. İkinci olarak, Python hiyerarşik veri yapılarına erişmek için süper basit bir sözdizimine sahiptir, yani düzenli öznitelik erişimi (Foo. Bar.Bam.name). Üçüncüsü, Python kullanmak, kullanıcıların yapılandırma özniteliklerini bir yapılandırma dosyasından diğerine aktarmasını kolaylaştırır. Dördüncüsü, Python dinamik olarak yazılmış olsa bile, çalışma zamanında kontrol edilebilen türlere sahiptir. Bu nedenle, bir yapılandırma dosyasındaki 1, '1' tamsayısı iken, '1' bir dizedir.

Çalışma zamanında buna ihtiyaç duyan sınıflara yapılandırma bilgilerini almak için tam otomatik bir yöntem. Belirli bir özniteliği çıkarmak için bir yapılandırma hiyerarşisinde yürüyen kod yazmak zahmetlidir. Yüzlerce öznitelik içeren karmaşık yapılandırma bilgilerine sahip olduğunuzda, bu ağlamak istemenize neden olur.

Çalışma zamanından önce tüm yapılandırma hiyerarşisinin statik olarak belirtilmesini gerektirmeyen tür denetimi ve doğrulama. Python çok dinamik bir dildir ve bir program başladığında yapılandırılması gereken her şeyi her zaman bilemezsiniz.

Bunu başarmak için temel olarak 3 nesne sınıfını ve bunların birbirleriyle ilişkilerini tanımlarlar:

1) Yapılandırma - temelde birleştirme için bazı geliştirmeler içeren bir Zincir Haritası / temel diktedir.

2) Yapılandırılabilir - yapılandırmak istediğiniz her şeyi alt sınıflara ayırmak için temel sınıf.

3) Uygulama - belirli bir uygulama işlevini gerçekleştirmek için somutlaştırılmış nesne veya tek amaçlı yazılım için ana uygulamanız.

Onların sözleriyle:

Uygulama: Uygulama

Başvuru, belirli bir işi yapan bir süreçtir. En belirgin uygulama ipython komut satırı programıdır. Her uygulama, bir veya daha fazla yapılandırma dosyasını ve tek bir komut satırı seçenekleri kümesini okur ve ardından uygulama için bir ana yapılandırma nesnesi üretir. Bu konfigürasyon nesnesi daha sonra uygulamanın oluşturduğu konfigüre edilebilir nesnelere aktarılır. Bu yapılandırılabilir nesneler, uygulamanın gerçek mantığını uygular ve yapılandırma nesnesi verildiğinde kendilerini nasıl yapılandıracaklarını bilirler.

Uygulamalar her zaman yapılandırılmış bir Logger olan bir günlük özniteliğine sahiptir. Bu, uygulama başına merkezi günlük yapılandırmasına izin verir. Yapılandırılabilir: Yapılandırılabilir

Yapılandırılabilir, bir uygulamadaki tüm ana sınıflar için temel sınıf görevi gören normal bir Python sınıfıdır. Yapılandırılabilir temel sınıf hafiftir ve yalnızca bir şey yapar.

Bu Yapılandırılabilir, kendisini nasıl yapılandıracağını bilen bir HasTraits alt sınıfıdır. Metadata config = True olan sınıf düzeyi özellikleri, komut satırı ve yapılandırma dosyalarından yapılandırılabilen değerler haline gelir.

Geliştiriciler, uygulamadaki tüm mantığı uygulayan Yapılandırılabilir alt sınıflar oluşturur. Bu alt sınıfların her biri, örneklerin nasıl oluşturulduğunu kontrol eden kendi yapılandırma bilgilerine sahiptir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.