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

100

В своем бесконечном поиске чрезмерно усложняющих простых вещей я исследую самый «питонический» способ предоставления глобальных переменных конфигурации внутри типичного файла 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 01
4
Часто я пишу файл конфигурации JSON. Его можно легко прочитать в структурах Python, а также создать с помощью инструмента. Кажется, что он обладает наибольшей гибкостью, и единственная стоимость - это некоторые скобки, которые могут раздражать пользователя. Однако я никогда не писал Яйца. Может, это стандартный способ. В таком случае просто проигнорируйте мой комментарий выше.
erikbwork 01
1
Вы можете использовать «vars (self)» вместо «self .__ dict __.
Keys
1
Возможный дубликат: Как лучше всего использовать файл настроек в Python? Они отвечают: «Возможны многие способы, и тема bikeshed уже существует. Config.py хорош, если вы не заботитесь о безопасности».
Никана Реклавикс
Я закончил использовать python-box, см. Этот ответ
эволюционировал

Ответы:

5

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

Кит
источник
6
Упомянутый basicconfig.pyфайл, похоже, переместился на github.com/kdart/pycopia/blob/master/core/pycopia/…
Пол М. Ферли
Я знаю, что ему несколько лет, но я новичок и думаю, что этот файл конфигурации - это, по сути, то, что я ищу (возможно, слишком продвинутый), и я хотел бы понять его лучше. Могу ли я просто передать инициализацию ConfigHolderс помощью набора конфигураций, который я хотел бы установить и передать между модулями?
Джинкс
@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, который вы должны загружать с диска каждый раз, когда хотите получить к нему доступ, в каждом отдельном классе. Я считаю, что вопрос в том, чтобы «загрузить один раз» и иметь глобальный доступ к загруженным данным. Как бы вы это сделали с предложенным вами решением?
omni
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, но для этого требуется больше кода обработки переменных для каждого элемента и он основан на объектах.
  • требует минимальных изменений для добавления новых элементов конфигурации и установки его изменчивости.

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

pds
источник
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и т. Д.
Эндрю Палмер,
Если вам нравится этот стиль, вы также можете использовать collections . 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)

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

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

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

eSurfsnake
источник
1
Не рекомендуется называть модуль именем globals, поскольку это встроенная функция, которая возвращает dict с каждым символом в текущей глобальной области видимости. Кроме того, PEP8 рекомендует CamelCase (со всеми заглавными буквами в акронимах) для классов (т.е. DBInfo) и прописными буквами с подчеркиванием для так называемых констант (т.е. DEBUG).
Nuno André
1
Спасибо @ NunoAndré за комментарий, пока я его не прочитал, я думал, что этот ответ делает что-то странное globals, автор должен изменить имя
oglop
Это мой подход. Однако я вижу много подходов, которые люди называют «лучшими». Можете ли вы указать на некоторые недостатки реализации config.py как это?
Яш Наг,
5

Давайте будем честными, нам, вероятно, следует рассмотреть возможность использования поддерживаемой библиотеки Python Software Foundation :

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

Минусы:

  • Неконтролируемое глобальное изменяемое состояние.
pds
источник
Использование файла .ini бесполезно, если вам нужно применить операторы if в других ваших файлах для изменения конфигурации. Вместо этого было бы лучше использовать config.py, но если значения не меняются, а вы просто вызываете и используете его, я согласен с использованием файла .ini.
Kourosh 05
3

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

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

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

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

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

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

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

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

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

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

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

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

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

По их словам:

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

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

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

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

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

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

jLi
источник