在 config.py 中提供全局配置变量的最 Python 方式是什么?

在我对过于复杂的简单东西的无尽追求中,我正在研究在 Python egg 包中发现的典型的“ 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))

这似乎是一种在包中存储和获取全局变量的更具可读性、表达性和灵活性的方法。

有史以来最烂的主意?处理这些情况的最佳实践是什么?在包中存储和获取全局名称和变量的 你的方法是什么?

157031 次浏览

我做过一次。最后,我发现我的简化 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 的回答相似,我建议用 lambda 函数构建它们来减少代码:

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

请查看 IPython 配置系统,该系统通过 Traitlet 实现,用于您正在手动执行的类型强制。

剪切和粘贴在这里,以符合 SO 的指导方针,不只是删除链接,因为链接的内容随着时间的推移发生变化。

Traitlets 文档

以下是我们希望配置系统具有的主要要求:

支持分层配置信息。

与命令行选项解析器完全集成。通常,您希望读取配置文件,但随后使用命令行选项覆盖某些值。我们的配置系统使这个过程自动化,并允许将每个命令行选项链接到它将覆盖的配置层次结构中的特定属性。

本身是有效 Python 代码的配置文件。这样可以完成很多事情。首先,可以在配置文件中放置逻辑,根据操作系统、网络设置、 Python 版本等设置属性。其次,Python 有一个超级简单的语法来访问层次化数据结构,即常规属性访问(Foo)。酒吧。名字)。第三,使用 Python 可以使用户轻松地将配置属性从一个配置文件导入到另一个配置文件。 第四,尽管 Python 是动态类型化的,但它确实有可以在运行时检查的类型。因此,配置文件中的1是整数‘1’,而‘1’是字符串。

一种完全自动化的方法,用于将配置信息提供给运行时需要它的类。编写遍历配置层次结构以提取特定属性的代码是痛苦的。当您拥有具有数百个属性的复杂配置信息时,这会让您想哭。

不需要在运行时之前静态指定整个配置层次结构的类型检查和验证。Python 是一种非常动态的语言,您并不总是知道程序启动时需要配置的所有内容。

为了实现这一点,他们基本上定义了3个对象类及其相互之间的关系:

1)配置-基本上是一个 ChainMap/基本结构,对合并有一些增强。

2)可配置基类来子类化所有你想要配置的东西。

3)应用程序-实例化的对象,以执行特定的应用程序功能,或您的单一用途的软件的主要应用程序。

用他们的话说:

应用: 应用

应用程序是执行特定工作的进程。最明显的应用程序是 ipython 命令行程序。每个应用程序读取一个或多个配置文件和一组命令行选项,然后为应用程序生成一个主配置对象。然后,该配置对象被传递给应用程序创建的可配置对象。这些可配置对象实现应用程序的实际逻辑,并且知道如何根据配置对象配置它们自己。

应用程序始终有一个已配置的 Logger 日志属性,这允许对每个应用程序进行集中的日志配置。 可配置: 可配置

可配置类是一个常规 Python 类,用作应用程序中所有主类的基类。可配置基类是轻量级的,只做一件事。

这个 Configable 是 HasTraits 的一个子类,它知道如何配置自己。具有元数据 config = True 的类级特性成为可以从命令行和配置文件配置的值。

开发人员创建实现应用程序中所有逻辑的可配置子类。每个子类都有自己的配置信息,用于控制如何创建实例。

我喜欢这个解决方案 小型应用:

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,但这需要更多的变量处理代码每个项目,是基于对象。
  • 需要最小的更改 来添加新的配置项并设置其可变性。

——编辑—— : 对于大型应用程序,将值存储在 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)

人们忘记了类的存在,即使从来没有实例化过属于该类的任何对象。以及类中没有“ self”的变量。在类的所有实例之间共享,即使没有实例。一旦“调试”被任何代码更改,所有其他代码都会看到更改。

通过将它作为 gl 导入,您可以拥有多个这样的文件和变量,它们允许您跨代码文件、函数等访问和设置值,但不会有名称空间冲突的危险。

这缺少其他方法的一些聪明的错误检查,但是很简单,容易理解。

老实说,我们可能应该考虑使用 Python软体基金会维护库:

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

缺点:

  • 不受控制的 全局可变的状态。