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

В своем бесконечном стремлении к усложнению простых вещей я исследую наиболее «питонский» способ предоставления глобальных переменных конфигурации внутри типичного « 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:%[email protected]%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))

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

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

69 голосов | спросил Rigel Di Scala 1 J0000006Europe/Moscow 2011, 12:35:24

7 ответов


0

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

ответил Keith 1 J0000006Europe/Moscow 2011, 12:47:28
0

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

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 1 J0000006Europe/Moscow 2011, 13:52:12
0

Похоже на ответ 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
ответил Cory B 17 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowWed, 17 Sep 2014 03:42:00 +0400 2014, 03:42:00
0

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

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

ответил pds 12 Maypm17 2017, 18:37:44
0

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

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

# main.py
from config import MYSQL

print(MYSQL.PORT) # 3306
ответил Husky 2 J000000Sunday17 2017, 01:41:51
0

Небольшая вариация идеи Хаски, которую я использую. Создайте файл с именем '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 13 +03002017-10-13T03:57:15+03:00312017bEurope/MoscowFri, 13 Oct 2017 03:57:15 +0300 2017, 03:57:15
0

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

Вырезайте и вставляйте сюда, чтобы соответствовать рекомендациям 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 14 PMpFri, 14 Apr 2017 18:55:11 +030055Friday 2017, 18:55:11

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132