关于python:在config.py中提供全局配置变量的大多数Pythonic方法?

Most Pythonic way to provide global configuration variables in config.py?

在我对过于复杂的简单事物的无尽探索中,我正在研究最"pythonic"的方法,在python egg包中发现的典型"config.py"中提供全局配置变量。

传统方式(aah,good ol'define!)如下:

1
2
3
MYSQL_PORT = 3306
MYSQL_DATABASE = 'mydb'
MYSQL_DATABASE_TABLES = ['tb_users', 'tb_groups']

因此,全局变量以以下方式之一导入:

1
2
3
4
from config import *
dbname = MYSQL_DATABASE
for table in MYSQL_DATABASE_TABLES:
    print table

或:

1
2
3
import config
dbname = config.MYSQL_DATABASE
assert(isinstance(config.MYSQL_PORT, int))

这是有道理的,但有时可能有点混乱,特别是当你试图记住某些变量的名称时。此外,提供一个以变量为属性的"配置"对象可能更灵活。因此,我从bpython config.py文件中了解到了以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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",它导入类并读取如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
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'

并以这种方式使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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))

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

有没有最蹩脚的想法?处理这些情况的最佳实践是什么?在包中存储和获取全局名称和变量的方法是什么?


只使用这样的内置类型如何:

1
2
3
4
5
6
7
8
9
10
config = {
   "mysql": {
       "user":"root",
       "pass":"secret",
       "tables": {
           "users":"tb_users"
        }
        # etc
    }
}

您可以访问以下值:

1
config["mysql"]["tables"]["users"]

如果您愿意牺牲在配置树中计算表达式的潜力,那么您可以使用yaml并最终得到一个更可读的配置文件,如下所示:

1
2
3
4
5
mysql:
  - user: root
  - pass: secret
  - tables:
    - users: tb_users

并使用类似pyyaml的库方便地解析和访问配置文件


类似于布卢布的回答。我建议使用lambda函数构建它们以减少代码。这样地:

1
2
3
4
5
6
7
8
9
10
11
12
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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")

然后用法是:

1
2
3
4
5
6
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,但这要求每个项处理更多的可变代码,并且是基于对象的。
  • 需要最少的更改才能添加新的配置项并设置其可变性。

--编辑——对于大型应用程序,将值存储在yaml(即属性)文件中并将其作为不可变数据读取是更好的方法(即blubb/ohaal的答案)。对于小型应用程序,上述解决方案更简单。


上课怎么样?

1
2
3
4
5
6
7
8
9
10
# 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想法的一个小变化。创建一个名为"globals"(或您喜欢的任何文件)的文件,然后在其中定义多个类,例如:

1
2
3
4
5
6
7
8
9
#globals.py

class dbinfo :      # for database globals
    username = 'abcd'
    password = 'xyz'

class runtime :
    debug = False
    output = 'stdio'

然后,如果有两个代码文件c1.py和c2.py,它们都可以位于顶部

1
import globals as gl

现在所有代码都可以访问和设置值,例如:

1
2
gl.runtime.debug = False
print(gl.dbinfo.username)

人们忘记类的存在,即使没有对象被实例化为类的成员。类中没有"self."开头的变量在类的所有实例之间共享,即使没有实例也是如此。一旦"调试"被任何代码更改,所有其他代码都会看到更改。

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

这缺乏对其他方法的一些巧妙的错误检查,但简单易行。


请查看ipython配置系统,该系统通过traitlets实现,用于手动执行类型。

剪切和粘贴在这里是为了遵守这样的准则,即随着链接内容的变化,不仅要删除链接。

列车员文件

Here are the main requirements we wanted our configuration system to have:

Support for hierarchical configuration information.

Full integration with command line option parsers. Often, you want to read a configuration file, but then override some of the values with command line options. Our configuration system automates this process and allows each command line option to be linked to a particular attribute in the configuration hierarchy that it will override.

Configuration files that are themselves valid Python code. This accomplishes many things. First, it becomes possible to put logic in your configuration files that sets attributes based on your operating system, network setup, Python version, etc. Second, Python has a super simple syntax for accessing hierarchical data structures, namely regular attribute access (Foo.Bar.Bam.name). Third, using Python makes it easy for users to import configuration attributes from one configuration file to another.
Fourth, even though Python is dynamically typed, it does have types that can be checked at runtime. Thus, a 1 in a config file is the integer ‘1’, while a '1' is a string.

A fully automated method for getting the configuration information to the classes that need it at runtime. Writing code that walks a configuration hierarchy to extract a particular attribute is painful. When you have complex configuration information with hundreds of attributes, this makes you want to cry.

Type checking and validation that doesn’t require the entire configuration hierarchy to be specified statically before runtime. Python is a very dynamic language and you don’t always know everything that needs to be configured when a program starts.

为此,他们基本上定义了3个对象类及其相互关系:

1)配置-基本上是一个链图/基本dict,具有一些用于合并的增强功能。

2)可配置-基类将您希望配置的所有内容子类化。

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

用他们的话说:

Application: Application

An application is a process that does a specific job. The most obvious application is the ipython command line program. Each application reads one or more configuration files and a single set of command line options and then produces a master configuration object for the application. This configuration object is then passed to the configurable objects that the application creates. These configurable objects implement the actual logic of the application and know how to configure themselves given the configuration object.

Applications always have a log attribute that is a configured Logger. This allows centralized logging configuration per-application.
Configurable: Configurable

A configurable is a regular Python class that serves as a base class for all main classes in an application. The Configurable base class is lightweight and only does one things.

This Configurable is a subclass of HasTraits that knows how to configure itself. Class level traits with the metadata config=True become values that can be configured from the command line and configuration files.

Developers create Configurable subclasses that implement all of the logic in the application. Each of these subclasses has its own configuration information that controls how instances are created.