Part 1 · Foundations · Module 02
Values, Types & Variables
Every program is about values: numbers, text, true and false, nothing at all. In this module you will
learn the basic kinds of values Python has, how arithmetic really works (including why 0.1 + 0.2 is
not exactly 0.3), and the single most important mental model in Python: names are labels that refer
to objects.
- Use
int,float,complex,bool, andNoneand say what each is for - Explain floating-point rounding and choose
math.isclose,decimal, orfractionswhen it matters - Apply operators and precedence, including
//,%,**, anddivmod - Describe names as references to objects, and tell
isapart from== - Explain dynamic and strong typing, and what mutable and immutable mean
- Convert between types and read user input with
input()
Values and types
A value is a piece of data: 42, 3.14, "hello",
True. Every value is an object in memory, and every object has a
type. The type decides two things: what the value can represent, and which operations work on
it. You can add two numbers; you cannot divide a word by a word.
| Type | Represents | Examples |
|---|---|---|
int | Whole numbers, any size | 0, -7, 10**100 |
float | Real numbers, approximately | 3.14, -0.5, 1e9 |
complex | Complex numbers | 2+3j |
bool | Truth values | True, False |
str | Text (next module) | "hi" |
NoneType | "No value here" | None |
Code that writes a value directly, like 42 or "hi", is called a literal.
(repr shows a value the way you would type it in code; the for loop repeats the
print for each value. You will study loops properly in M04.)
Integers: whole numbers of any size
An int is a whole number, positive, negative, or zero. Many languages limit integers to 64 bits
(about 9.2 quintillion). Python integers have arbitrary precision: they grow as large as your
memory allows. You never get an "overflow".
Underscores can make long literals readable: 1_000_000 is the same as 1000000. You can
also write integers in other bases: 0b1010 (binary), 0o12 (octal), and
0xA (hexadecimal) are all ten.
Above: how the integer is stored as bits (binary digits, 0 or 1). Change the value and watch the bits change.
Floats: real numbers, approximately
A float ("floating-point number") represents a real number like 3.14 or
-0.001. Any literal with a decimal point or an exponent (2.5e3 means 2.5 × 10³) is a
float, and dividing with / always gives a float: 6 / 2 is 3.0.
Why 0.1 + 0.2 is not 0.3
This surprises everyone, and it is not a Python bug. Floats follow the IEEE-754 standard used by
virtually every CPU. A float is stored in 64 bits: 1 sign bit, 11 exponent bits, and 52 bits of fraction. Just as
1/3 cannot be written exactly in decimal (0.3333…), 1/10 cannot be written exactly in binary. So
0.1 is stored as the nearest representable binary fraction, which is a tiny bit more than 0.1. Small
errors add up.
Look at the fraction bits for 0.1: the pattern 1001 1001 1001… repeats forever in binary, so it must
be cut off and rounded. Try 0.5 or 0.25: those are exact, because they are powers of two.
What to do about it
math.isclose
Never compare floats with ==. Ask whether they are
close enough: math.isclose(a, b) uses a relative tolerance of about nine decimal digits.
decimal.Decimal
Exact decimal arithmetic. Use it for money. Create decimals
from strings: Decimal("0.1"), not Decimal(0.1).
fractions.Fraction
Exact rational numbers such as 1/3. Useful in maths and whenever you must avoid rounding entirely.
The special float values inf (infinity) and nan ("not a number", the result of
undefined operations) also come from IEEE-754. nan is not even equal to itself.
Complex numbers
Python also has a built-in complex type, written with j for the imaginary unit (as
engineers do): (1 + 2j) * (3 - 1j) is (5+5j). Use .real and
.imag to get the parts. You will mostly meet complex numbers in scientific code.
Remember: floats are fast approximations; compare them with math.isclose; use
Decimal for money and Fraction for exact ratios.
Operators and precedence
An operator is a symbol that combines values, like +. The values it works on are
called operands.
| Operator | Meaning | Example | Result |
|---|---|---|---|
+ - * | Add, subtract, multiply | 7 * 3 | 21 |
/ | True division (always a float) | 7 / 2 | 3.5 |
// | Floor division: round down to a whole number | 7 // 2 | 3 |
% | Modulo: the remainder | 7 % 2 | 1 |
** | Power | 2 ** 10 | 1024 |
// and % are partners: for any a and nonzero b,
(a // b) * b + a % b == a. The built-in divmod(a, b) returns both at once. They are
everywhere in real programs: converting minutes into hours and minutes, checking if a number is even
(n % 2 == 0), wrapping around a clock.
Precedence
Precedence is the order in which operators are applied, just like in school maths. From highest
to lowest: **, then unary minus -x, then * / // %, then + -,
then comparisons (< == != and so on), then not, and, or.
Operators on the same level go left to right, except **, which goes right to left.
Augmented assignment
Updating a variable based on its old value is so common that there is a shortcut. count += 1 means
count = count + 1. There are versions for every operator: -=, *=,
/=, //=, %=, **=.
bool and None
A Boolean (named after mathematician George Boole) is one of two values: True or
False. Comparisons produce Booleans: 3 < 5 is True. Python lets you chain
comparisons the way mathematicians write them: 0 <= age < 130.
Combine Booleans with and (both must be true), or (at least one), and
not (flip it). Surprisingly, bool is a kind of int: True
behaves like 1 and False like 0, so sum of a list of Booleans counts the true ones.
None is Python's way of saying "nothing" or "no value yet". It is the only value of its type. A
function that does not return anything returns None, which is why print(print("hi"))
prints hi and then None.
Names are references to objects
This section contains the most important idea in the module. In some languages a variable is a box that holds a
value. In Python, a variable is a name that refers to an object, like a luggage
tag tied to a suitcase. Assignment x = 42 does two things: it makes (or finds) the object
42, and it ties the tag x to it. This is called binding a name.
Consequences:
y = xdoes not copy anything. It ties a second tag to the same object. Two names for one object are called aliases.- Assigning a new value to a name (rebinding) moves that tag to a different object. Other names are unaffected.
Explore the scenarios: alias (two names, one list), rebind (moving a tag), and immutable (what happens with numbers).
Step through the same idea with the tracer. Watch the "obj N" identity tags: when two names show the same tag, they refer to the same object.
After line 3, cart also contains "pear", because there was only ever one list. Line 4 rebinds
cart to a brand-new list, and same_cart still refers to the old one.
Identity vs equality: is vs ==
Every object has an identity, a unique number for as long as it exists, which
id(obj) returns. Two different questions follow:
a == b: are they equal (same value)?a is b: are they the same object (same identity)?
Dynamic typing, strong typing, and mutability
Dynamic typing
In Python, names have no type; objects do. A name can refer to an int now and a string later. This is dynamic typing: types are checked while the program runs, not before. (In statically typed languages such as Java, a variable's type is fixed when you write the code.)
Strong typing
Python is also strongly typed: it does not silently convert between unrelated types.
"3" + 4 raises a TypeError rather than guessing whether you meant "34" or
7. "Explicit is better than implicit." (Numbers do mix sensibly: 1 + 2.5 is
3.5.)
Mutable and immutable
An object is mutable if it can be changed in place after it is created, and
immutable if it cannot. All the types in this module (int, float,
bool, None, and also str) are immutable. Lists are mutable.
This is why x = x + 1 never surprises you with numbers: the int object 10 cannot change, so Python
makes a new object 11 and rebinds x. Any alias still sees 10. With a list, append changes
the one shared object, and every alias sees the change.
Type conversion and input()
Each type's name is also a function that converts to it:
| Call | Result | Note |
|---|---|---|
int("42") | 42 | Parses text; whitespace is ignored |
int(3.99) | 3 | Truncates towards zero; does not round |
float("2.5") | 2.5 | |
str(42) | "42" | Works on anything |
bool(0), bool("") | False | Zero and empty are false; almost everything else is true |
int("3.5") | ValueError | Not a whole-number string |
Reading input
input(prompt) shows the prompt, waits for the user to type a line and press Enter, and returns what
they typed, always as a string. If you need a number, convert it.
In the browser, typed input is pre-filled for you (here 21 and 4.5). When you run the
script locally with pixi run python, you type the answers.
Practice
Given total_minutes = 135, compute hours and
minutes using // and % (or divmod), then print exactly
2 h 15 m. Your code must work for any value of total_minutes.
Hint
135 // 60 is the whole hours; 135 % 60 is
what is left over.
Show solution
total_minutes = 135
hours, minutes = divmod(total_minutes, 60)
print(hours, "h", minutes, "m")
The average of 10, 20, and 30 should be 20.0, but this code computes
40.0. Fix it with parentheses so average is correct.
Hint
/ has higher precedence than +, so only
c is being divided.
Show solution
a, b, c = 10, 20, 30
average = (a + b + c) / 3
print(average)
Set close to the result of checking, with math.isclose,
whether 0.1 + 0.2 is close to 0.3. Then set total to the exact
Decimal sum of the prices "19.99", "5.01", and "0.10"
(create each Decimal from a string).
Hint
Decimal("19.99") + Decimal("5.01") + ...
Show solution
import math
from decimal import Decimal
close = math.isclose(0.1 + 0.2, 0.3)
total = Decimal("19.99") + Decimal("5.01") + Decimal("0.10")
print(close, total)
Starting from original = [1, 2], make alias refer to the
same list and copy refer to a new list with the same items (use
list(original)). Then append 3 through alias. Afterwards
original must be [1, 2, 3] and copy must still be [1, 2].
Hint
alias = original ties a second name to the same list.
Use alias.append(3) to mutate it.
Show solution
original = [1, 2]
alias = original
copy = list(original)
alias.append(3)
Read a quantity (a whole number) and a unit price (a decimal number) with two calls
to input(). Print the total cost: quantity × price. With the inputs 3 and
2.5, your program prints 7.5. Store the converted values in quantity and
price.
Hint
input returns strings. Wrap each call:
int(input(...)).
Show solution
quantity = int(input("Quantity? "))
price = float(input("Price? "))
print(quantity * price)
Mini-project: a unit converter
Quiz
Recap
- Every value is an object with a type; the type decides what operations make sense.
inthas arbitrary precision;floatis IEEE-754 and approximate.- Compare floats with
math.isclose; useDecimalfor money andFractionfor exact ratios. //floors,%gives the remainder,**is power and binds tightest; parenthesize when unsure.- Names are references. Assignment binds;
y = xcreates an alias; rebinding moves one name only. ==compares values,iscompares identity.- Python is dynamically and strongly typed. Numbers and strings are immutable; lists are mutable.
input()returns a string; convert it withint()orfloat().
Going further
- Floating-point arithmetic: issues and limitations
- Numeric types in the standard library reference
decimalandfractions- Operator precedence table
- Ned Batchelder: Facts and myths about Python names and values
- Next: M03 · Strings & Text