Наиболее подходящие для Python способ обеспечить глобальные переменные конфигурации в config.py?



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



традиционный способ (ААА, добрый старина #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' это импортирует класс и читает следующим образом:



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


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



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

722   7  

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.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, вы можете иметь несколько таких файлов и переменных, которые позволяют вам получать доступ и устанавливать значения в файлах кода, функциях и т. д., но без опасности столкновения пространства.

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

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

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

traitlets documentation

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

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

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

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

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

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

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

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

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

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

по их словам:

Приложения:

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

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

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

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

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

Comments

    Ничего не найдено.