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

159 投票
8 回答
192178 浏览
提问于 2025-04-16 18:45

在我不断把简单事情搞复杂的过程中,我正在研究在Python的egg包中,如何以最“Pythonic”的方式提供全局配置变量,通常是在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)])

然后,一个导入这个类的'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))

这似乎是一种更易读、更具表现力和灵活的方式来存储和获取包中的全局变量。

这算是最糟糕的主意吗?在这种情况下,最佳实践是什么?你是如何在你的包中存储和获取全局名称和变量的呢?

8 个回答

43

我觉得这个方法 适合小型应用

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的回答)。 对于小型应用,上面的解决方案更简单。

68

你可以直接使用内置的数据类型,像这样:

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这样的库,方便地解析和访问配置文件。

5

我之前也做过一次。最后我发现我简化后的 basicconfig.py 足够满足我的需求。如果需要的话,你可以传入一个包含其他对象的命名空间,让它进行引用。你还可以从你的代码中传入额外的默认值。它还将属性和映射样式的语法映射到同一个配置对象上。

撰写回答