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.

⏱ ~2 hoursPrerequisites: M01

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.

TypeRepresentsExamples
intWhole numbers, any size0, -7, 10**100
floatReal numbers, approximately3.14, -0.5, 1e9
complexComplex numbers2+3j
boolTruth valuesTrue, False
strText (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.

OperatorMeaningExampleResult
+ - *Add, subtract, multiply7 * 321
/True division (always a float)7 / 23.5
//Floor division: round down to a whole number7 // 23
%Modulo: the remainder7 % 21
**Power2 ** 101024

// 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:

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:

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:

CallResultNote
int("42")42Parses text; whitespace is ignored
int(3.99)3Truncates towards zero; does not round
float("2.5")2.5
str(42)"42"Works on anything
bool(0), bool("")FalseZero and empty are false; almost everything else is true
int("3.5")ValueErrorNot 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

Mini-projectUnit converter

Build convert.py in your practice workspace. It asks for a temperature in Celsius, a distance in kilometres, and a duration in seconds, and prints conversions.

Temperature in Celsius? 21.5
21.5 C = 70.7 F
Distance in km? 10
10.0 km = 6.214 miles
Duration in seconds? 4000
4000 s = 1 h 6 min 40 s

Specification

  • Fahrenheit = Celsius × 9 / 5 + 32. One mile is 1.609344 km.
  • Use round(value, 1) or round(value, 3) for display.
  • Convert seconds with divmod twice (seconds → minutes and seconds, minutes → hours and minutes).
  • Run with pixi run python convert.py; add a convert task.

Acceptance criteria

  • Every input() result is converted with int() or float() before arithmetic
  • 21.5 °C prints 70.7 °F; 0 °C prints 32.0 °F; -40 °C prints -40.0 °F
  • 10 km prints 6.214 miles
  • 4000 seconds prints 1 h 6 min 40 s using divmod
  • Constants like the km-per-mile factor are named variables (for example KM_PER_MILE)
  • pixi run convert works, and pixi run ruff format convert.py leaves it tidy

Stretch goals

  • Add a currency conversion using Decimal so cents never drift.
  • Type abc at a prompt and read the ValueError traceback. (You will learn to handle it gracefully in M07.)
  • Add kilograms to pounds (1 lb = 0.45359237 kg).

Quiz

Recap

Going further