from fractions import Fraction
values = (7, Fraction(7, 3), 7 / 3)
[(type(value).__name__, value) for value in values][('int', 7), ('Fraction', Fraction(7, 3)), ('float', 2.3333333333333335)]
After completing this unit, you will be able to:
Local prerequisites: Unit 1, sets and functions at the A30 level, and basic operations on finite vectors. This unit does not yet require NumPy.
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][('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.
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)(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.
A good computational function specifies three things:
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)])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.
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)((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.
An invariant is a property that must remain true during a particular operation. For scalar multiplication of vectors, testable invariants include:
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.
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.
Choose an initial representation for each of the following objects and explain its limitations: , a numerical approximation to , and the fixed vector .
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.
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 ==.
Write the preconditions, result, and failure behavior for a function that returns the least nonnegative remainder when is divided by modulus .
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.
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.
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.
== compares values; is compares object identity.