Python only what you need
Define the score labeler input contract
Page 2 hardens the boundary around the threshold score labeler (`scores.py`) so bad inputs fail before the interesting algorithm runs.
1Learn the idea
Read
Define what may enter
The accepted input remains: a list of numeric scores in 0..1 and a threshold in 0..1. Keep parsing and normalization in functions that do not score, train, or call a model. That split lets a test fail the boundary without blaming the core logic. The user-facing decision stays: accept or reject a model score using one shared cutoff.
Read
Reject at the boundary
def require_number(value, name):
if isinstance(value, bool) or not isinstance(value, (int, float)):
raise TypeError(f"{name} must be a number")
value = float(value)
if not 0.0 <= value <= 1.0:
raise ValueError(f"{name} must be between 0 and 1")
return value
def require_scores(values):
if not isinstance(values, list):
raise TypeError("scores must be a list")
out = []
for index, value in enumerate(values):
out.append(require_number(value, f"score[{index}]"))
return out
def validate(scores, threshold):
return require_scores(scores), require_number(threshold, "threshold")
print(validate([0.2, 0.9, 0.4], 0.5))
Expected output:
([0.2, 0.9, 0.4], 0.5)
This contract intentionally rejects numeric strings. Silently accepting "0.9" in one place but comparing strings elsewhere creates inconsistent behavior. It also rejects booleans: Python treats True as the integer 1, but a truth value is not a score in this contract.
Negative tests:
for scores, threshold in [([0.2, "0.9"], 0.5), ([True], 0.5), ([1.2], 0.5), ([0.2], -0.1)]:
try:
validate(scores, threshold)
except (TypeError, ValueError) as error:
print(type(error).__name__, error)
Each case must print a field-named error; none should reach labeling.
Read
Keep transforms testable
Write one assertion for a neighboring valid input to the score labeler so tightening the boundary does not over-reject. Document field names and types the way a teammate would need them on day two of python-only-what-you-need—not as comments you plan to delete.
Read
Lab notebook: name the fields
List every field in scores=[0.2,0.9,0.4], threshold=0.5 and mark each as required, optional, or forbidden. Required fields must fail loudly when missing; optional fields need defaults you can quote in a test; forbidden fields (secrets, raw PII, path escapes) must never be accepted silently. This list is the contract for the score labeler.
Add one sentence about encoding, units, or timezones if relevant to a list of numeric scores in 0..1 and a threshold in 0..1. Contracts that ignore units create “correct” programs that still ship wrong decisions when someone tries to accept or reject a model score using one shared cutoff.
Read
Worked judgment
Write the error string you want for the most likely bad input. Prefer ValueError('threshold out of range')-style messages over generic invalid input. The contract’s job is to make string scores that compare lexicographically, or IndentationError that hides a wrong cutoff harder to confuse with a model or algorithm bug later.
Read
Independent transfer
Adapt the contract for integer percentages from 0 through 100. Decide whether 90.0, "90", and True are accepted, then write one valid-neighbor test beside every rejection.
Go deeper
Before you start
Why this matters
Invent one malformed input that the threshold score labeler (scores.py) might accidentally accept. Predict the exception or rejection message. After you run the contract code, compare your prediction with the real failure text.
In the wild
See how this idea shows up as a product and a company — then come back to the lesson. Skills transfer across vendors.
Related lessons
Check your understanding
Page assessment
Answer from memory. Completion is saved from this evidence, not from opening the next page.
All responses are required.