Найбільш пітонічний спосіб надання глобальних змінних конфігурації в config.py? [зачинено]


99

У своєму нескінченному пошуку надто ускладнюючих простих речей, я досліджую найбільш «Pythonic» спосіб надання глобальних змінних конфігурації всередині типового « config.py », який міститься в пакунках для яєць Python.

Традиційний спосіб (так, добре, #define !) Такий :

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

Тому глобальні змінні імпортуються одним із наступних способів:

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

або:

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

Це має сенс, але іноді може бути трохи брудно, особливо коли ви намагаєтеся запам'ятати імена певних змінних. Крім того, надання об'єкта "конфігурації" зі змінними як атрибутами може бути більш гнучким. Отже, беручи участь у файлі bpython config.py, я придумав:

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

і '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'

і використовується таким чином:

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

Що здається більш читабельним, виразним та гнучким способом зберігання та отримання глобальних змінних всередині пакету.

Найгарніша ідея коли-небудь? Яка найкраща практика для подолання цих ситуацій? Що ваш спосіб зберігання і вибірки глобальні імена і змінні всередині пакету?


3
Ви вже прийняли тут рішення, яке може бути, а може не бути добрим. Саму конфігурацію можна зберігати різними способами, наприклад, JSON, XML, різні граматики для * nixes та Windows тощо. Залежно від того, хто пише файл конфігурації (інструмент, людина, яке тло?), Різні граматики можуть бути кращими. Найчастіше не годиться дозволяти файл конфігурації писати тією ж мовою, якою ви користуєтесь для своєї програми, оскільки це надає занадто багато сили користувачеві (що може бути вами самим, але ви самі можете не пам'ятати все, що може помилитися на кілька місяців вперед).
erikbwork

4
Часто я в кінцевому підсумку пишу файл конфігурації JSON. Його можна легко прочитати в структурах python, а також створити за допомогою інструменту. Здається, він має найбільшу гнучкість, і єдина вартість - деякі брекети, які можуть дратувати користувача. Однак я ніколи не писав Яйце. Можливо, це стандартний спосіб. У такому випадку просто ігноруйте мій коментар вище.
erikbwork

1
Ви можете використовувати "vars (self)" замість "self .__ dict __.
Keys

1
Можливий дублікат Яка найкраща практика використання файлу налаштувань у Python? Вони відповідають: "Існує багато способів, і вже існує нитка" bikeshed ". Config.py добре, якщо ви не дбаєте про безпеку".
Nikana Reklawyks

Я закінчив користуватися python-box, див. Цю відповідь
еволюціонував

Відповіді:


5

Я зробив це одного разу. Зрештою, я знайшов свій спрощений basicconfig.py, який відповідає моїм потребам. Ви можете передати в просторі імен інші об’єкти, щоб він міг посилатися, якщо вам це потрібно. Ви також можете передати додаткові значення за замовчуванням зі свого коду. Він також пов'язує синтаксис атрибутів та стилів відображення з тим самим об'єктом конфігурації.


6
basicconfig.pyФайл називається , здається, переїхав в github.com/kdart/pycopia/blob/master/core/pycopia / ...
Paul M Ферли

Я знаю, що цьому кілька років, але я новачок, і я думаю, що цей конфігураційний файл - це, по суті, те, що я шукаю (можливо, занадто просунутий), і я хотів би зрозуміти його краще. Чи я просто передаю ініціалізацію ConfigHolderза допомогою dict конфігурацій, які я хотів би встановити, і передавати між модулями?
Jinx

@Jinx На цьому етапі я б використовував (і зараз використовую) файл YAML та PyYAML для конфігурації. Я також використовую сторонній модуль, який називається, confitі він підтримує об'єднання декількох джерел. Це частина нового модуля devtest.config .
Кіт

57

Як щодо того, щоб просто використовувати вбудовані типи, як це:

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

Ви отримаєте доступ до значень наступним чином:

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

Якщо ви готові пожертвувати потенціалом для обчислення виразів у вашому дереві конфігурації, ви можете використовувати YAML і в кінцевому підсумку отримати більш читабельний файл конфігурації, такий як:

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

і використовуйте бібліотеку, як PyYAML, для зручного аналізу та доступу до конфігураційного файлу


Але зазвичай ви хочете мати різні конфігураційні файли і, отже, не мати даних про конфігурацію у коді. Отже, «config »буде зовнішнім файлом JSON / YAML, який ви повинні завантажувати з диска кожного разу, коли ви хочете отримати до нього доступ, у кожному окремому класі. Я вважаю, що питання полягає в тому, щоб "завантажити один раз" і мати глобальний доступ до завантажених даних. Як би ви це зробили із запропонованим рішенням?
masi

3
якби просто щось існувало для збереження даних у пам’яті ^^
cinatic

16

Мені подобається це рішення для невеликих додатків :

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

І тоді використання:

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

.. вам це повинно сподобатися, тому що:

  • використовує змінні класу (жоден об'єкт для передачі / не потрібен синглтон),
  • використовує інкапсульовані вбудовані типи і виглядає як (є) викликом методу App,
  • має контроль над незмінністю індивідуальної конфігурації , мінливі глобальні системи - це найгірший тип глобальних процесів .
  • сприяє звичайному та добре названому доступу / зручності читання у вихідному коді
  • є простим класом, але забезпечує структурований доступ , альтернативою є використання @property, але для цього потрібно більше коду обробки змінних для кожного елемента і базується на об'єкті.
  • вимагає мінімальних змін для додавання нових елементів конфігурації та встановлення його змінності.

--Редагувати-- : Для великих програм кращим підходом є зберігання значень у файлі YAML (тобто властивостей) та зчитування цього як незмінних даних (тобто відповідь blubb / ohaal ). Для невеликих додатків це рішення простіше.


9

Як щодо використання класів?

# 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. Я пропоную побудувати їх за допомогою лямбда-функцій, щоб зменшити код. Подобається це:

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

Однак це пахне так, ніби ви захочете зробити клас.

Або, як зазначив MarkM, ти міг би використати 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
pass- невдале ім'я змінної, оскільки це також ключове слово.
Thomas Schreiter

О так ... я щойно зібрав цей німий приклад. Я зміню назву
Cory-G

Для такого підходу ви можете розглянути клас замість mkDictлямбди. Якщо ми зателефонуємо до нашого класу User, ваші ключі словника "config" будуть ініціалізовані приблизно так {'st3v3': User('password','blonde','Steve Booker')}. Коли ваш "користувач" знаходиться у userзмінній, ви можете отримати доступ до його властивостей як user.hairтощо
Ендрю Палмер,

Якщо вам подобається цей стиль, ви також можете вибрати колекцію.namedtuple . User = namedtuple('User', 'passwd hair name'); config = {'st3v3': User('password', 'blonde', 'Steve Booker')}
MarkM

7

Невелика варіація ідеї Хаскі, яку я використовую. Створіть файл під назвою 'globals' (або що завгодно), а потім визначте в ньому кілька класів як таких:

#globals.py

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

class runtime :
    debug = False
    output = 'stdio'

Тоді, якщо у вас є два файли коду c1.py та c2.py, обидва можуть мати вгорі

import globals as gl

Тепер весь код може отримувати доступ і встановлювати значення як такі:

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

Люди забувають про існування класів, навіть якщо ніколи не створюється екземпляр об’єкта, який є членом цього класу. І змінні в класі, яким не передує "self". діляться між усіма екземплярами класу, навіть якщо таких немає. Як тільки `` налагодження '' змінюється будь-яким кодом, всі інші коди бачать зміни.

Імпортуючи його як gl, ви можете мати кілька таких файлів і змінних, що дозволяє отримувати доступ та встановлювати значення у файлах коду, функціях тощо, але без загрози зіткнення простору імен.

У цьому бракує розумної перевірки помилок інших підходів, але він простий і простий у виконанні.


1
Неправильно радити називати модуль globals, оскільки це вбудована функція, яка повертає dict з кожним символом у поточній глобальній області. Крім того, PEP8 рекомендує CamelCase (з усіма великими літерами в абревіатурах) для класів (тобто DBInfo) та великі регістри з підкресленнями для так званих констант (тобто DEBUG).
Нуно Андре

1
Дякую @ NunoAndré за коментар, поки я не прочитав його, я думав, що ця відповідь робить щось дивне globals, автор повинен змінити назву
oglop

Цей підхід - це моя ідея. Однак я бачу багато підходів, які, на думку людей, є "найкращими". Чи можете ви вказати деякі недоліки реалізації config.py як цей?
Яш Наг,

5

Будемо чесними, нам, мабуть, варто задуматись про використання бібліотеки, що підтримується Фондом Python :

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

Приклад конфігурації: (формат ini, але доступний JSON)

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

[bitbucket.org]
User = hg

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

Приклад коду:

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

Зробити це глобально доступним:

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

Недоліки:

  • Неконтрольований глобальний змінний стан.

Корисно використовувати файл .ini, якщо вам потрібно застосувати оператори if в інших файлах для зміни конфігурації. Краще використовувати замість цього config.py, але якщо значення не змінюються, а ви просто викликаєте та використовуєте його, я погоджуюсь із використанням файла .ini.
Kourosh

3

будь ласка, ознайомтесь із системою конфігурації IPython, реалізованою за допомогою трайтлетів, для типового застосування, яке ви робите вручну.

Вирізати та вставити тут, щоб відповідати вимогам SO, щоб не просто скидати посилання, оскільки вміст посилань змінюється з часом.

документація про третлети

Ось основні вимоги, які ми хотіли, щоб мала наша система конфігурації:

Підтримка ієрархічної інформації про конфігурацію.

Повна інтеграція з синтаксичними аналізаторами командного рядка. Часто потрібно прочитати файл конфігурації, але потім замінити деякі значення параметрами командного рядка. Наша конфігураційна система автоматизує цей процес і дозволяє кожному параметру командного рядка пов’язати певний атрибут в ієрархії конфігурації, який він буде замінювати.

Файли конфігурації, які самі є дійсним кодом Python. Це робить багато речей. По-перше, стає можливим розмістити логіку у файлах конфігурації, яка встановлює атрибути на основі вашої операційної системи, налаштування мережі, версії Python тощо. По-друге, Python має надзвичайно простий синтаксис для доступу до ієрархічних структур даних, а саме до звичайного доступу до атрибутів (Foo. Bar.Bam.name). По-третє, використання Python полегшує користувачам імпорт атрибутів конфігурації з одного конфігураційного файлу в інший. По-четверте, навіть незважаючи на те, що Python динамічно набирається, він має типи, які можна перевірити під час виконання. Таким чином, 1 у конфігураційному файлі є цілим числом "1", тоді як "1" - це рядок.

Повністю автоматизований метод отримання інформації про конфігурацію до класів, яким вона потрібна під час виконання. Написання коду, який виконує ієрархію конфігурації для вилучення певного атрибута, є болючим. Коли у вас є складна інформація про конфігурацію з сотнями атрибутів, це викликає бажання плакати.

Перевірка та перевірка типу, які не вимагають статичної ієрархії конфігурації перед виконанням. Python - це дуже динамічна мова, і ви не завжди знаєте все, що потрібно налаштувати під час запуску програми.

Щоб досягти цього, вони в основному визначають 3 класи об'єктів та їх відносини між собою:

1) Конфігурація - в основному ChainMap / базовий дикт з деякими вдосконаленнями для злиття.

2) Настроюваний - базовий клас для підкласу всіх речей, які ви хочете налаштувати.

3) Додаток - об'єкт, створений для створення певної функції програми, або ваш основний додаток для одноцільового програмного забезпечення.

За їх словами:

Застосування: Застосування

Додаток - це процес, який виконує певну роботу. Найбільш очевидним додатком є ​​програма командного рядка ipython. Кожна програма читає один або кілька файлів конфігурації та один набір параметрів командного рядка, а потім створює основний об'єкт конфігурації для програми. Потім цей об’єкт конфігурації передається конфігурованим об’єктам, які створює програма. Ці об'єкти, що налаштовуються, реалізують фактичну логіку програми та знають, як налаштувати себе, задавши об'єкт конфігурації.

Програми завжди мають атрибут log, який є налаштованим Logger. Це дозволяє конфігурацію централізованого ведення журналу для кожного додатка. Налаштовується: Налаштовується

Настроюваний - це звичайний клас Python, який служить базовим класом для всіх основних класів у програмі. Налаштований базовий клас є легким і робить лише одне.

This Configurable - це підклас HasTraits, який знає, як налаштувати себе. Ознаки рівня класу з метаданими config = True стають значеннями, які можна налаштувати за допомогою командного рядка та файлів конфігурації.

Розробники створюють налаштовувані підкласи, які реалізують всю логіку програми. Кожен із цих підкласів має власну інформацію про конфігурацію, яка контролює спосіб створення екземплярів.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.