Самый Pythonic способ предоставить глобальные переменные конфигурации в config.py?

в моем бесконечном поиске в чрезмерно усложняющих простых вещах я исследую самый "Питонический" способ предоставления глобальных переменных конфигурации внутри типичного"config.py ' найдено в пакетах с яйцами Python.

традиционный способ (aah, хороший ol' #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)])

и a 'config.py-это импортирует класс и гласит:--9-->

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

, который кажется более четкий, выразительный и гибкий способ хранения и извлечения глобальных переменных внутри пакета.

отстойная идея? Какова наилучшая практика преодоления таких ситуаций? Что такое код способ хранения и извлечения глобальных имен и переменных внутри вашего пакета?

7 ответов


Я сделал это один раз. В конце концов я нашел свой упрощенный basicconfig.py адекватный для моих потребностей. При необходимости вы можете передать пространство имен с другими объектами для ссылки на него. Вы также можете передать дополнительные значения по умолчанию из своего кода. Он также сопоставляет атрибут и синтаксис стиля сопоставления с тем же объектом конфигурации.


как насчет просто использовать встроенные типы, как это:

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 для обычного анализа и доступа к конфигурационному файлу


похоже на ответ 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

Это пахнет, как вы можете сделать класс, хотя.

или, как отметил Маркм, вы можете использовать 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

мне нравится это решение для небольших приложений:

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

.. вам должно понравиться, потому что:

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

-- Edit--: Для больших приложений хранение значений в файле YAML (т. е. свойств) и чтение в качестве неизменяемых данных-лучший подход (т. е. blubb/ohaal это). Для небольших приложений это решение выше проще.


Как насчет использования классов?

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

# main.py
from config import MYSQL

print(MYSQL.PORT) # 3306

небольшая вариация на идею Хаски, которую я использую. Сделайте файл под названием "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)

люди забывают о существовании классов, даже если ни один объект никогда не создается, который является членом этого класса. И переменные в классе не предшествует "я". являются общими для всех экземпляров класса, даже если их нет. Как только "debug" изменяется любым кодом, весь другой код видит изменение.

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

Это не хватает некоторых умных ошибок проверки других подходов, но просто и легко следовать.


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

вырезать и вставить здесь, чтобы соответствовать рекомендациям SO для не просто удаления ссылок, поскольку содержание ссылок меняется с течением времени.

документация по traitlets

вот основные требования, которые мы хотели, чтобы наша система конфигурации имела:

поддержка иерархической конфигурации информация.

полная интеграция с синтаксическими анализаторами командной строки. Часто требуется прочитать файл конфигурации, но затем переопределить некоторые значения с помощью параметров командной строки. Наша система конфигурации автоматизирует этот процесс и позволяет каждому параметру командной строки быть связанным с определенным атрибутом в иерархии конфигурации, который он переопределит.

файлы конфигурации, которые сами являются допустимым кодом Python. Это многое решает. Во-первых, это становится возможным поместить логику в файлы конфигурации, которые устанавливают атрибуты на основе вашей операционной системы, настройки сети, версии Python и т. д. Во-вторых, Python имеет супер простой синтаксис для доступа к иерархическим структурам данных, а именно регулярный доступ к атрибутам (Foo.Бар.БАМ.имя). В-третьих, использование Python позволяет пользователям легко импортировать атрибуты конфигурации из одного файла конфигурации в другой. В-четвертых, хотя Python динамически типизирован, у него есть типы, которые можно проверить на во время выполнения. Таким образом, 1 в конфигурационном файле-это целое число "1", а " 1 " - строка.

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

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

чтобы достичь этого, они в основном определяют 3 класса объектов и их отношения друг к другу:

1) конфигурация-в основном ChainMap / basic dict с некоторыми улучшениями для слияния.

2) конфигурируемый-базовый класс для подкласса всех вещей, которые вы хотели бы конфигурировать.

3) Application-объект, который создается для выполнения определенной функции приложения или основного приложения для программного обеспечения одной цели.

по их словам:

Приложения:

приложение-это процесс, который выполняет определенную работу. Наиболее очевидным приложением является программа командной строки ipython. Каждое приложение считывает один или несколько файлов конфигурации и один набор параметров командной строки и затем создает объект master configuration для приложения. Затем этот объект конфигурации передается настраиваемым объектам, создаваемым приложением. Эти настраиваемые объекты реализуют фактическую логику приложения и знают, как настроить себя с учетом объекта конфигурации.

приложения всегда имеют атрибут журнала, который настроен логгер. Это позволяет централизованной конфигурации ведения журнала для каждого приложения. Настраиваемый: Настраиваемый

конфигурируемый-это обычный класс Python, который служит базовым классом для всех основных классов в приложении. Настраиваемый базовый класс является легким и делает только одну вещь.

Это настраиваемый подкласс HasTraits, который знает, как настроить себя. Черты уровня класса с метаданными config=True становятся значениями, которые можно настроить из командной строки и файлов конфигурации.

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