There are two core design patterns for the scope of objects used to configure a Python application:
- A global property map: A global object can contain all of the configuration parameters. A simple class definition is perhaps an ideal way to provide names and values; this tends to follow the Singleton design pattern ensuring that only one instance exists. Alternatives include a dictionary with pairs of name: value, or a types.SimpleNamespace object of attribute values.
- Object construction: Instead of a single object, we'll define a kind of Factory or collection of Factories that use the configuration data to build the objects of the application. In this case, the configuration information is used once when a program is started and never again. The configuration information isn't kept around as a global object.
The global property map design is very popular because it is simple and extensible. The first example of this is using a class object, which is defined as follows:
class Configuration: some_attribute = "default_value"
We can use the preceding class definition as a global container of attributes. During the initialization, we might have something similar to this as part of parsing the configuration file:
Configuration.some_attribute = "user-supplied value"
Everywhere else in the program, we can use the value of Configuration.some_attribute. A variation on this theme is to make a more formal Singleton class design pattern. This is often done with a global module, as it can be easily imported in a way that provides us with an accessible global definition.
The second example involves using a module for the configuration. We might have a module named configuration.py. In that file, we can have a definition like the following:
settings = {
"some_attribute": "user-supplied value"
}
Now, the application can use configuration.settings as a global repository for all of the application's settings. A function or class can parse the configuration file, loading this dictionary with the configuration values that the application will then use.
In a Blackjack simulation, we might see the following code:
shoe = Deck(configuration.settings['decks'])
Alternatively, we might possibly see the following code:
If bet > configuration.settings['limit']: raise InvalidBet()
Generally, we'll try to avoid having a global variable for the configuration. Because a global variable is implicitly present everywhere, it can be misused to carry stateful processing in addition to configuration values. Instead of a global variable, we can often handle the configuration relatively more neatly through object construction. In the next section, we will look at examples of constructing objects as a way to implement the configuration changes.