Configuration via class definitions

The difficulty that we sometimes have with top-level script configuration is the lack of handy default values. To provide defaults, we can use ordinary class inheritance. Here's how we can use the class definition to build an object with the configuration values:

class Example2(simulation.AppConfig):
dealer_rule = Hit17()
split_rule = NoReSplitAces()
table = Table(
decks=6, limit=50, dealer=dealer_rule, split=split_rule, payout=(3, 2)
)
player_rule = SomeStrategy()
betting_rule = Flat()
player = Player(play=player_rule, betting=betting_rule, max_rounds=100, init_stake=50)
outputfile = Path.cwd()/"data"/"ch14_simulation2b.dat"
samples = 100

This allows us to define the AppConfig class with default configuration values. The class that we've defined here, Example2, can override the default values defined in the AppConfig class.

We can also use mixins to break the definition down into reusable pieces. We might break our classes down into the table, player, and simulation components and combine them via mixins. For more information on the mixin class design, see Chapter 9, Decorators and Mixins – Cross-Cutting Aspects.

In two small ways, this use of a class definition pushes the envelope on object-oriented design. This kind of class has no method definitions; we're only going to use this class as a Singleton object. However, it is a very tidy way of packing up a small block of code so that the assignment statements fill in a small namespace.

We can modify our simulate() function to accept this class definition as an argument:

def simulate_c(config: Union[Type[AppConfig], SimpleNamespace]) -> None:
simulator = Simulate(config.table, config.player, config.samples)
with Path(config.outputfile).open("w", newline="") as results:
wtr = csv.writer(results)
wtr.writerow(simulator)

This function has picked out the relevant values, config.table, config.player, and config.samples, from the overall configuration object and used them to build a Simulate instance and execute that instance. The results are the same as the previous simulate() function, but the argument structure is different. Here's how we provide the single instance of the class to this function:

if __name__ == "__main__": 
    simulation.simulate_c(Example2) 

Note that we're not providing an instance of the Example2 class. We're using the class object itself. The Type[AppConfig] type hint shows the class itself is expected, not an instance of the class.

One potential disadvantage of this approach is that it is not compatible with argparse to gather command-line arguments. We can solve this by defining the interface to be compatible with a types.SimpleNamespace object. This overlap is formalized in the type hint: Union[Type[AppConfig], SimpleNamespace]. This type definition permits a wide variety of objects to be used to provide configuration parameters.

In addition to using a class, we can also create a SimpleNamespace object to have a similar-looking syntax for using the configuration parameter values.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset