Самый Pythonic способ предоставить глобальные переменные конфигурации в config.py? - PullRequest
76 голосов
/ 01 июня 2011

В своем бесконечном стремлении к усложнению простых вещей я исследую наиболее «Pythonic» способ предоставления глобальных переменных конфигурации внутри типичного « config.py », найденного в пакетах яиц Python.

Традиционный способ (ааа, добрый старик # определение !) Следующий:

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

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

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

Ответы [ 7 ]

50 голосов
/ 01 июня 2011

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

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

9 голосов
/ 17 сентября 2014

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

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
8 голосов
/ 12 мая 2017

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

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's answer ). Для небольших приложений это решение, указанное выше, является более простым.

5 голосов
/ 02 июля 2017

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

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

# main.py
from config import MYSQL

print(MYSQL.PORT) # 3306
5 голосов
/ 01 июня 2011

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

3 голосов
/ 13 октября 2017

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

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

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

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

1 голос
/ 14 апреля 2017

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

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

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

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

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

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

Файлы конфигурации, которые сами по себе являются допустимым кодом Python.Это достигает многих вещей.Во-первых, становится возможным поместить логику в ваши файлы конфигурации, которая устанавливает атрибуты на основе вашей операционной системы, настроек сети, версии Python и т. Д. Во-вторых, Python имеет супер простой синтаксис для доступа к иерархическим структурам данных, а именно регулярный доступ к атрибутам (Foo.Bar.Bam.name).В-третьих, использование Python позволяет пользователям легко импортировать атрибуты конфигурации из одного файла конфигурации в другой.В-четвертых, несмотря на то, что Python динамически типизирован, у него есть типы, которые можно проверять во время выполнения.Таким образом, 1 в файле конфигурации - это целое число «1», а «1» - строка.

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

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

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

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

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

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

Иными словами:

Приложение: Приложение

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

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

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

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

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

...