1

I have created a .ini-like file with all the values which I need later in my program, see below:

[debugging]
checkForAbort = 10
...

[official]
checkForAbort = 30
...

I would like to read all these items into a single class and make it accessible from other parts of my python project. What I have so far is the code below:

from ConfigParser import SafeConfigParser
import glob

class ConfigurationParameters
    def __init__(self):
        self.checkForAbortDuringIdleTime = None     

    parser = SafeConfigParser()

    # making a list here in case we have multiple files in the future!
    candidatesConfiguration = ['my.cfg']
    foundCandidates = parser.read(candidatesConfiguration)
    missingCandidates = set(candidatesConfiguration) - set(found)
    if foundCandidates:
        print 'Found config files:', sorted(found)
    else
        print 'Missing files     :', sorted(missing)
        print "aborting..."


    # check for mandatory sections below
    for candidateSections in ['official', 'debugging']:
        if not parser.has_section(candidateSections)
            print "the mandatory section", candidateSections " is missing"
            print "aborting..."

    for sectionName in ['official', 'debugging']:
        for name, value in parser.items(section_name):
            self.name = value

I am new to python but I can still see lots of problem with my code:

  • I am forced to add a attribute for each item in my class file. and keep the configuration file and my class in sync all the time.
  • This class is not a singleton, therefore the reading/parsing will be done from wherever it is imported!
  • If a value is added to the config-file with is not defined in my class, it will probably crash!

How should I solve this problem instead? Can class attributes be created dynamically?

My class only need to read from the values, so no writing to the configuration file is needed!

4

3 回答 3

12

What J.F. Sebastian said.

Also, you could do it like Alex Martelli does in his Bunch class:

file MyConfig.py:

from ConfigParser import SafeConfigParser


section_names = 'official', 'debugging'


class MyConfiguration(object):

    def __init__(self, *file_names):
        parser = SafeConfigParser()
        parser.optionxform = str  # make option names case sensitive
        found = parser.read(file_names)
        if not found:
            raise ValueError('No config file found!')
        for name in section_names:
            self.__dict__.update(parser.items(name))  # <-- here the magic happens


config = MyConfiguration('my.cfg', 'other.cfg')

file foo.py:

from MyConfig import config
# ...

file MyProgram.py:

from MyConfig import config

print config.checkForAbort

import foo

assert config is foo.config

The Python Language Reference states that "Import statements are executed in two steps: (1) find a module, and initialize it if necessary; (2) define a name or names in the local namespace (of the scope where the import statement occurs)."

What that means is that, when a module gets imported, one or more local names are bound to a module object, and only the first time it is imported during the runtime of a Python program it gets initialized (i.e. read from file and run).

In the code above the name config is just a local name that refers to an attribute of a module object. The module object has been initialized by the Python interpreter when it was referenced (via from MyConfig import config) in MyProgram. When MyProgram imports foo it is already initialized and gets bound to a local name in module foo and in MyProgram we can refer to it as foo.config. But both names refer to the very same object.

于 2012-09-27T13:02:23.237 回答
3

self.name = value doesn't work as you expect. You might mean setattr(self, name, value) It creates instance attributes dynamicallly.

To make it a singleton you could make the instance to be a global variable in config/settings module. Initialize it once on a program startup e.g., like the logging module does it for the root logger: logging.config.fileConfig('logging.conf').

It is a common desire to use an attribute access for what is essentially a dict e.g., argparse.Namespace that carries command-line options.

You could access the config later by importing it: from mypackage.config import config, e.g., mypackage/config.py:

class Config(object):
    ...

config = Config() # empty or default config without any configuration

In mypackage/__main__.py:

from .config import config
...
# initialization code
config.update_from_file('mypackage.conf') # it might call `setattr()` inside

Note: setattr() works correctly even you set properties. __dict__.update() breaks in this case:

class XValidatorMixin(object):
    @property
    def x(self):
        return self._x

    @x.setter
    def x(self, value):
        if value < 0:
            raise ValueError
        self._x = value


class CUpdate(XValidatorMixin):
    def __init__(self, **options):
        self.__dict__.update(options)


class CSetAttr(XValidatorMixin):
    def __init__(self, **options):
        for name, value in options.items():
            setattr(self, name, value)

for C in [CUpdate, CSetAttr]:
    o = C(a=1, b=2) # non-property attributes work as expected
    assert o.a == 1 and o.b == 2

o = CSetAttr(x=1)
assert o.x == 1 # setattr also sets property

o = CUpdate(x=1)
try:
    o.x # .update() doesn't set property
except AttributeError:
    pass
else:
    assert 0


try:
    o = CSetAttr(x=-1)  # invokes property setter
except ValueError: # that correctly reject negative values
    pass
else:
    assert 0
于 2012-09-27T12:02:06.380 回答
0

The Answer by pillmuncher is very helpful and can be easily adapted to use within an existing class. Furthermore, it is also possible to automatically convert the data types using the localconfig module (link). To get these additional functionalities you can use something like:

from localconfig import LocalConfig

configfile = 'config.ini'
config = LocalConfig(configfile)
sections = list(config)

for section in sections:
    items = list(config.items(section))
    CLASS.__dict__.update(items)
于 2017-12-13T12:26:26.003 回答