最 Python 方式在 config.py 中提供全局配置变量



在我对过于复杂化简单东西的无休止的追求中,我正在研究最'Pythonic'的方法,以在Python蛋包中发现的典型">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))

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

有史以来最蹩脚的想法?应对这些情况的最佳实践是什么?包中存储和获取全局名称和变量的方法是什么?

只使用这样的内置类型怎么样:

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

我喜欢这个适用于小型应用程序的解决方案:

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

老实说,我们可能应该考虑使用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)
 ....

缺点:

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

我使用的赫斯基想法的一个小变体。 制作一个名为"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,您可以拥有多个此类文件和变量,这些文件和变量允许您跨代码文件、函数等访问和设置值,但没有命名空间冲突的危险。

这缺乏其他方法的一些聪明的错误检查,但简单易懂。

类似于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

我做过一次。最终,我发现我的简化 basicconfig.py 足以满足我的需求。如果需要,可以传入包含其他对象的命名空间以供引用。还可以从代码中传入其他默认值。它还将属性和映射样式语法映射到同一配置对象。

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

在此处剪切和粘贴以符合 SO 准则,即不会在链接内容随时间变化时删除链接。

叛徒文档

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

支持分层配置信息。

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

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

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

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

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

1(配置 - 基本上是一个链图/基本字典,有一些合并的增强功能。

2(可配置 - 基类到子类您希望配置的所有内容。

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

用他们的话说:

应用

:应用

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

应用程序始终具有作为配置记录器的日志属性。这允许每个应用程序进行集中日志记录配置。 可配置:可配置

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

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

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

相关内容

  • 没有找到相关文章

最新更新