5  Objects, Values, and Functions

5.1 Learning objectives

After completing this unit, you will be able to:

  • choose suitable Python representations for integers, rational numbers, and finite sequences;
  • distinguish equality of values from identity of objects;
  • write functions with stated preconditions, results, and error behavior;
  • avoid unintended changes through mutable inputs;
  • formulate invariants that tests can check; and
  • produce structured output that does not depend on incidental ordering.

Local prerequisites: Unit 1, sets and functions at the A30 level, and basic operations on finite vectors. This unit does not yet require NumPy.

5.2 Mathematical kinds and program types

Mathematical statements specify sets of objects and valid operations. Programs need concrete data types. The two are related, but they are not identical.

Mathematical intent Initial representation Important limitation
integer int practical size is limited by memory
rational number Fraction numerators and denominators can grow
approximation to a real number float rounding and finite range
fixed finite vector tuple length and component types must be checked
collection intended to change list aliases can observe the same changes
from fractions import Fraction

values = (7, Fraction(7, 3), 7 / 3)
[(type(value).__name__, value) for value in values]
Listing 5.1
[('int', 7), ('Fraction', Fraction(7, 3)), ('float', 2.3333333333333335)]

A type name does not prove that a value meets its mathematical intent. A tuple of length three can represent a vector in three-dimensional space, color coordinates, or three unrelated pieces of data. The program contract supplies that additional meaning.

5.3 Values, identity, and aliases

The == operator compares values according to the type’s rules. The is operator checks whether two names refer to the same Python object. For reproducibility, do not replace a question about values with a question about object identity.

a = [1, 2, 3]
b = a
c = list(a)

(a == c, a is c, a is b)
Listing 5.2
(True, False, True)

Because b is an alias for a, a change made through either name is visible through the other. By contrast, c initially has the same value but is a different list. Effects like this can make an experiment depend on the order in which cells are executed.

If a vector is intended to be an immutable value, tuple expresses that contract more faithfully than list. This is not a ban on lists; choose mutation only when it is part of the method.

5.4 Functions as contracts

A good computational function specifies three things:

  1. Preconditions: which inputs are accepted?
  2. Result: what value is returned when the preconditions hold?
  3. Failure: what happens when a precondition is violated?

The following example computes the mean of rational numbers exactly.

from fractions import Fraction

def mean_fraction(values):
    """Return the exact mean of a nonempty sequence of rational numbers."""
    values = tuple(Fraction(value) for value in values)
    if not values:
        raise ValueError("values must not be empty")
    return sum(values, start=Fraction(0)) / len(values)

mean_fraction([Fraction(1, 3), Fraction(1, 2)])
Listing 5.3
Fraction(5, 12)

Converting the input to a tuple freezes the values the function actually uses. Once tuple construction is complete, changes to the original list do not affect these local values. This conversion does not promise an atomic snapshot if another thread mutates the input during construction.

5.5 Pure functions and changes of state

The following function multiplies a vector by a scalar without changing its argument.

def scale_vector(scalar, vector):
    return tuple(scalar * component for component in vector)

v = (2, -1, 4)
(scale_vector(3, v), v)
Listing 5.4
((6, -3, 12), (2, -1, 4))

Given the same inputs, a pure function produces the same output and does not change any state outside the function. This makes testing and tracing the origin of results easier. Real programs still need to read files or write output; place those effects at the program’s boundaries rather than spreading them throughout every mathematical function.

5.6 Invariants and testing

An invariant is a property that must remain true during a particular operation. For scalar multiplication of vectors, testable invariants include:

  • the length of the vector does not change;
  • multiplying by 1 preserves the value;
  • multiplying by 0 produces the zero vector; and
  • scale_vector(a*b, v) equals scale_vector(a, scale_vector(b, v)).

Tests on many examples can detect violations in the implementation. An algebraic proof is still needed to establish the properties for all scalars and all vectors in the intended domain.

5.7 Canonical output

Two experiments can produce the same information but different bytes because of field order, spacing, or line endings. When output serves as evidence, specify a canonical form. The Unit 2 script:

python source/code/unit02_objects.py --output output/unit02-results.json

writes UTF-8 JSON with sorted field names, fixed indentation, and LF line endings. The file’s hash then identifies a particular sequence of bytes that can be compared.

5.8 Exercises

5.8.1 Exercise 1 - choosing representations

Choose an initial representation for each of the following objects and explain its limitations: 2/72/7, a numerical approximation to 2\sqrt{2}, and the fixed vector (1,0,1)(1,0,-1).

Distinguish exact values from approximations, and fixed values from collections that will be changed.

Use Fraction(2, 7) for the exact rational number, float for a numerical approximation to the square root of two with a stated tolerance, and tuple for the fixed vector. A float does not store the square root of two exactly, and a tuple does not by itself guarantee that all its components are numeric.

5.8.2 Exercise 2 - values and identity

Create two distinct lists with the value [1, 2]. Show one comparison that is true and one that is false. Explain the results.

Use == and is.

If a=[1,2] and b=[1,2], then a == b is true because their values are equal, whereas a is b is false because they are different objects. Mathematical questions about whether lists have the same elements in the same order normally use ==.

5.8.3 Exercise 3 - a function contract

Write the preconditions, result, and failure behavior for a function that returns the least nonnegative remainder when aa is divided by modulus mm.

The modulus must be positive.

Preconditions: a and m are integers and m > 0. Result: an integer r such that 0 <= r < m and a-r is divisible by m. If m <= 0, the function must reject the input, for example by raising ValueError.

5.8.4 Exercise 4 - a dangerous alias

Explain why a function that appends an element to its input list can make an experiment’s results depend on notebook cell order. Give one remedy.

Another name may refer to the same list.

The first call changes the list that a second call later uses. Running cells in a different order produces a different initial state. Possible remedies include returning a new list, using a tuple for a fixed value, or making the mutation explicit and resetting the state before the experiment.

5.8.5 Exercise 5 - tests and proof

Does testing the four scale_vector invariants on a million vectors prove that the implementation is correct for all inputs? Give a precise answer.

Separate the tested domain from the universal domain.

No. These tests provide strong evidence on the sample and can find counterexamples, but they do not cover every input. General correctness of the function is established by checking the implementation’s definition against the distributive and associative laws in the stated domain of scalars and components.

5.9 Summary

  • Program types represent mathematical objects but do not determine their full meaning.
  • == compares values; is compares object identity.
  • Functions should specify preconditions, results, and failure behavior.
  • Pure functions and immutable values make reproduction and testing easier.
  • Invariants connect mathematical contracts with program tests.
  • Canonical output makes artifact hashes meaningful.