0%

Dataclasses and Value Objects · practice

Build Mutable Defaults Safely

A number can be shared safely as a default because it cannot be changed. A is different: each needs its own.

Dataclasses refuse the tempting version:

from dataclasses import dataclass


@dataclass
class QuizAttempt:
    learner: str
    responses: list = []

If one list were created when the class was defined, every attempt would receive the same object.

Ask for a new list each time

Use field(default_factory=list):

Try it

eq=False turns off generated equality. A QuizAttempt represents one attempt, so two separate attempts should remain unequal even when their learner and responses match. This preserves the entity identity from Chapter 7.

The factory is the list, not a call to it. The generated constructor calls that function each time it needs a default, producing a fresh list for the new instance.

Why is this written default_factory=list rather than default_factory=list()?

The bug can hide

Shared defaults are especially dangerous when the usual program creates only one object. The second object may arrive months later, after the first has accumulated data.

Use a factory for mutable per-object starting state:

Starting valueField definition
New empty listfield(default_factory=list)
New empty field(default_factory=dict)
Number or A direct default

The exercise gives both a configuration and an attempt their own lists. The grader creates pairs and changes only one member of each pair.

Task

Give each its own default.

QuizConfig has a required title and tags starting as a new empty per configuration.

QuizAttempt has a required learner and responses starting as a new empty list per attempt. Use eq=False because attempts are identity-based entities.

Use field(default_factory=list) for both lists. The grader changes one object and verifies the other remains empty.