Using Python with exec() for the configuration

When we decide to use Python as the notation for a configuration, we can use the exec() function to evaluate a block of code in a constrained namespace. We can imagine writing configuration files that look like the following code:

# SomeStrategy setup

# Table
dealer_rule = Hit17()
split_rule = NoReSplitAces()
table = Table(decks=6, limit=50, dealer=dealer_rule,
split=split_rule, payout=(3,2))

# Player
player_rule = SomeStrategy()
betting_rule = Flat()
player = Player(play=player_rule, betting=betting_rule,
max_rounds=100, init_stake=50)

# Simulation
outputfile = Path.cwd()/"data"/"ch14_simulation3a.dat"
samples = 100

This is a pleasant, easy-to-read set of configuration parameters. It's similar to the INI file and properties file that we'll explore in the following section. We can evaluate this file, creating a kind of namespace, using the exec() function:

code = compile(py_file.read(), "stringio", "exec")
assignments: Dict[str, Any] = dict()
exec(code, globals(), assignments)
config = SimpleNamespace(**assignments)

simulate(config.table, config.player, config.outputfile, config.samples)

In this example, the code object, code, is created with the compile() function. Note that this isn't required; we can simply provide the text of the file to the exec() function and it will compile the code and execute it.

The call to exec() provides three arguments:

  • The compiled code object
  • A dictionary that should be used to resolve any global names
  • A dictionary that will be used for any locals that get created

When the code block is finished, the assignment statements will have been used to build values in the local dictionary; in this case, the assignments variable. The keys will be the variable names. This is then transformed into a SimpleNamespace object so it's compatible with other initialization techniques mentioned previously.

The assignments dictionary will have a value that looks like the following output:

{'betting_rule': Flat(),
'dealer_rule': Hit17(),
'outputfile': PosixPath('/Users/slott/mastering-oo-python-2e/data/ch14_simulation3a.dat'),
'player': Player(play=SomeStrategy(), betting=Flat(), max_rounds=100, init_stake=50, rounds=100, stake=50),
'player_rule': SomeStrategy(),
'samples': 100,
'split_rule': NoReSplitAces(),
'table': Table(decks=6, limit=50, dealer=Hit17(), split=NoReSplitAces(), payout=(3, 2))}

This is used to create a SimpleNamespace object, config. The namespace object can then be used by the simulate() function to perform the simulation. Using a SimpleNamespace object makes it easier to refer to the individual configuration settings. The initial dictionary requires code such as assignments['samples']. The resulting config object can be used with code such as config.samples

The next section is a digression on why using exec() to parse Python code is not a security risk.

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

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