在config.py中提供全局配置变量的最Pythonic方法?[关闭]


98

在我对过度复杂的简单事物的无尽追求中,我正在研究最“ Pythonic”的方法来在Python egg包中的典型“ 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))

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

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


3
您已经在此处做出可能不错或可能不好的决定。配置本身可以以不同的方式存储,例如JSON,XML,* nixes和Windows的不同语法等等。取决于谁编写配置文件(工具,人员,背景?),不同的语法可能更可取。通常,最好以与您的程序使用的相同语言编写配置文件,因为这会给用户带来过多的力量(可能是您自己,但您自己可能不会记住所有可以几个月后会出错)。
erikbwork 2011年

4
通常,我最终会写一个JSON配置文件。它可以轻松地读入python结构,也可以通过工具创建。它似乎具有最大的灵活性,唯一的成本是一些可能会使用户烦恼的牙套。我从来没有写过鸡蛋。也许这是标准方法。在这种情况下,请忽略我上面的评论。
erikbwork 2011年

1
您可以使用“ vars(self)”代替“ self .__ dict __。keys()”
Karlisson 2013年

1
可能重复使用Python中的设置文件的最佳做法什么?他们回答“有许多可能,并且已经存在一个自行车道线程。除非您担心安全性,否则config.py很好。”
Nikana Reklawyks

我最终使用python-box,看到了这个答案
演变

Answers:


4

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


6
basicconfig.py提到的文件似乎已经移到github.com/kdart/pycopia/blob/master/core/pycopia/…–
Paul M Furley

我知道这已经有几年了,但是我是一个初学者,我认为这个配置文件实质上是我想要的(也许太高级了),我想更好地理解它。我是否只是通过ConfigHolder要设置并在模块之间传递的配置命令来传递initialize ?
金克斯(Jinx)

@Jinx此时,我将使用(并且当前正在使用)YAML文件和PyYAML进行配置。我还使用了一个称为的第三方模块confit,它支持合并多个源。它是新的devtest.config模块的一部分。
基思(Keith)

56

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

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


但是通常您希望拥有不同的配置文件,因此代码内没有任何配置数据。因此,“ config”将是一个外部JSON / YAML文件,您每次要访问它时,就必须在每个类中从磁盘加载该文件。我相信问题是要“加载一次”,并且可以对加载的数据进行全局访问。您将如何使用建议的解决方案来做到这一点?
masi

3
如果只是存在某种可以将数据保存在内存中的^^
cinatic '18

16

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

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


9

使用类怎么样?

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

# main.py
from config import MYSQL

print(MYSQL.PORT) # 3306

8

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

3
pass是一个不幸的变量名,因为它也是一个关键字。
Thomas Schreiter 2014年

哦,是的...我只是整理了这个愚蠢的例子。我改个名字
科里-G

对于这种方法,您可以考虑使用类而不是mkDictlambda。如果我们调用我们的类User,则您的“ config”字典键将被初始化为{'st3v3': User('password','blonde','Steve Booker')}。当你的“用户”是一个user变量,你就可以访问它的属性user.hair,等等
安德鲁·帕尔默

如果您喜欢这种样式,还可以选择使用collections.namedtupleUser = namedtuple('User', 'passwd hair name'); config = {'st3v3': User('password', 'blonde', 'Steve Booker')}
MarkM '18年

7

我使用的赫斯基想法略有不同。创建一个名为“ 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,您可以拥有多个这样的文件和变量,使您可以跨代码文件,函数等访问和设置值,但不会发生名称空间冲突的危险。

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


1
不建议将其命名为模块globals,因为它是一个内置函数,该函数返回一个字典,其中包含当前全局范围内的每个符号。此外,PEP8建议将CamelCase(首字母大写,首字母缩写)用于类(即DBInfo),并建议将大写字母加下划线表示所谓的常数(即DEBUG)。
NunoAndré18年

1
感谢@NunoAndré的评论,直到我读完它,我一直在想这个答案与某些事情有所不同globals,作者应该更改名称
oglop

这种方法是我去的。但是,我看到许多人认为是“最佳”的方法。您能说明实现config.py的一些缺点吗?
Yash Nag

5

坦白地说,我们可能应该考虑使用Python Software Foundation维护的库:

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

缺点:

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

如果您需要在其他文件中应用if语句来更改配置,则使用.ini文件不是很有用。最好改用config.py,但如果值没有变化,而您只是调用并使用它,则我同意使用.ini文件。
Kourosh

3

请检出通过traitlet实现的IPython配置系统,以实现您正在手动执行的类型强制。

在此处进行剪切和粘贴,以符合SO准则,而不仅仅是随着链接的内容随时间变化而删除链接。

特征文档

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

支持分层配置信息。

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

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

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

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

为此,他们基本上定义了3个对象类以及它们之间的关系:

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

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

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

用他们的话说:

应用:应用

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

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

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

此Configurable是HasTraits的子类,它知道如何进行自我配置。具有元数据config = True的类级别特征变为可以从命令行和配置文件配置的值。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.