Recording Experiment Configuration
Keep the choices that define a run in one explicit configuration record. Separate inputs and settings from computed results, pass configuration into the experiment, validate important constraints, and serialize simple values when the run must be recorded.
An experiment depends on choices made before it runs: a seed, number of trials, sample size, split fraction, input filename, or algorithm setting. If these values are scattered through a script, it becomes difficult to tell which choices produced a result.
An experiment configuration is an explicit record of the inputs and settings that define one run.
These names are clearer than unexplained literals inside a calculation, but a structured record keeps the related values together and makes them easier to pass, validate, display, and save.
Separate configuration from computed results
Configuration contains values known before the computation:
seed
trial count
sample size
split fraction
input data identifier
algorithm settings
Results contain values produced by the computation:
sampled values
measured duration
error
final score
output filename
For a coin simulation:
Keeping them separate answers two different questions:
- Configuration: What did the program intend to run?
- Measurements: What happened when it ran?
Do not place a result such as heads_count in the configuration merely because
it is convenient to store everything in one object.
Define a configuration with a dataclass
A dataclass gives the record named fields and a visible type:
Create one configuration by naming every field:
Keyword arguments make the meaning of each value visible at the call site. The class annotation records the expected types for readers and static tools; as explained in the records chapter, type hints do not automatically validate values at runtime.
frozen=True prevents ordinary field reassignment:
config.seed = 22
This raises an error rather than silently changing the description of a run. It is most useful when configuration fields are simple immutable values. A frozen dataclass does not recursively freeze a mutable list or dictionary stored inside it.
Validate constraints close to the configuration
Type hints cannot express rules such as “trial count must be positive” or
“probability must lie between zero and one.” A dataclass can check these rules
in __post_init__:
Validation should reject an invalid run before it produces a misleading or partial result.
The closed interval for probability is deliberate:
0.0 <= heads_probability <= 1.0
Both 0.0 and 1.0 are valid probabilities, although they produce
deterministic outcomes.
Pass configuration into the computation
A function that accepts one configuration makes its external settings visible:
The function reads all run settings from config. It does not depend on hidden
module-level values and does not choose a new seed internally.
This pattern supports comparison:
The two configurations differ in one named field. A later comparison can state that trial count was the controlled change.
Derive values without hiding their source
Some operational values are calculated from configuration. A split boundary, for example, may depend on example count and training fraction:
The configuration should record the primary choices. The result record can
also save an important derived value such as train_count so the exact
boundary is visible.
Avoid storing two independent fields that should always agree:
Here, train_count conflicts with the other settings. Calculate it from the
primary fields or validate the relationship explicitly.
Convert a dataclass to a saved record
dataclasses.asdict converts dataclass fields to an ordinary dictionary:
For simple Python strings, numbers, Boolean values, lists, and dictionaries, the resulting record can be written as JSON:
If configuration contains paths, enums, NumPy values, or custom objects,
convert them to an intentional stable representation first. For example, store
a path as a string and a NumPy scalar with .item().
Do not serialize fields merely because they exist. Exclude secrets, access tokens, and unnecessary personal information.
Run, display, and serialize one configuration
Edit one configuration value, then compare the printed configuration and measurements.
Run an experiment from validated configuration
The dataclass contains inputs known before the run. The function returns measurements, and the final record keeps the two groups separate.
Ready to run.
The JSON output is a representation of the run, not the configuration object used by the program. Keeping both concepts distinct prevents file-format details from controlling the internal design unnecessarily.
Use configuration to make changes explicit
When comparing two runs, begin with a baseline configuration:
Create a new configuration for the changed run:
Do not mutate the baseline and then describe it as though it were unchanged. A new immutable record preserves both sets of choices.
For a dataclass with many fields, dataclasses.replace can express one
controlled change:
The result is a new configuration. The baseline remains unchanged.
Which value belongs in experiment configuration rather than measurements?
Select one choice, then check.
HintUse the before-and-after distinction
Ask whether the value is known before the computation begins.
SolutionThe seed belongs to configuration
The seed defines the generator's starting state before the run. Observed count and elapsed time are measurements produced during the run.
What does @dataclass(frozen=True) provide for a configuration with integer
and floating-point fields?
Select one choice, then check.
HintDistinguish three responsibilities
Immutability, validation, and serialization are not the same feature.
SolutionFrozen prevents ordinary reassignment
The generated dataclass blocks statements such as config.seed = 22.
Runtime constraints still need validation, and JSON output still needs
conversion.
Which condition accepts every valid probability, including 0.0 and 1.0?
Select one choice, then check.
HintInclude both endpoints
Use <= on both sides.
SolutionUse the closed interval
0.0 <= probability <= 1.0 accepts impossible events with probability zero,
certain events with probability one, and every value between them.
A configuration stores example_count=100, train_fraction=0.8, and
train_count=70. What is the main problem?
Select one choice, then check.
HintDerive the boundary
The stated rule gives int(100 * 0.8).
SolutionThe redundant fields disagree
The derived training count is 80, not 70. Store the primary choices and
derive the count, or validate the relationship explicitly.
Complete the dataclass validation and create config so the program prints:
seed: 37
trial count: 12
record keys: ['heads_probability', 'seed', 'trial_count']
Build a serializable configuration
Ready to run.
HintComplete three separate steps
Check trial_count <= 0 and 0.0 <= heads_probability <= 1.0, construct
CoinExperimentConfig(seed=37, trial_count=12, heads_probability=0.5), and
call asdict(config).
SolutionValidate, construct, and convert
__post_init__ rejects an invalid count or probability before the experiment
runs. The constructor gives every setting a named field, and asdict
produces the three-field record.
Retain the configuration method
For each experiment:
identify choices known before the run
-> group them in a named configuration
-> validate constraints and relationships
-> pass configuration into the computation
-> derive dependent values explicitly
-> save configuration beside measurements
-> create a new record for each controlled change
Configuration makes an experiment's intended inputs visible. Saved results then show what those inputs produced.