Every Python program is about values: numbers, pieces of text, lists of things. This unit explains what a value really is (an object), how names refer to objects, and what the built in types are and how they behave. You will also meet the traps that catch people coming from other languages: two names silently sharing one list, is that "works" in a quick test and fails in production, and 0.1 + 0.2 not being 0.3.
The model you build here, names point at objects, is the key to the rest of the course. Function arguments, copying, default values and many bugs only make sense once you have it.
You will be able to:
- Explain how names refer to objects, and inspect objects with
id,typeandisinstance - Choose correctly between
==andis, and test forNonethe idiomatic way - Classify the built in types as mutable or immutable, hashable or unhashable
- Work with
int,float,Decimal,Fraction,complex,boolandNone, and avoid their traps - Convert between types, name things according to PEP 8, and use every form of assignment
- Copy nested data correctly with shallow and deep copies
Objects and names
Everything Python works with is an object: numbers, strings, lists, functions, even types themselves. Every object has three things:
- an identity, which never changes while the object exists;
- a type, which decides what the object can do, and which also never changes;
- a value, which may or may not be changeable, depending on the type.
A name (what other languages call a variable) is a label that refers to an object. Assignment with = binds a name to an object. It never copies anything. That is why the code below creates only two lists, not three:
a and b are two names for list #1, so a change made through b is visible through a. c refers to a different list that happens to hold equal values. Names don't have types; objects do. A name can be rebound to an object of any type at any time.
id, type and isinstance
Three built in functions let you inspect an object:
id(obj)returns an integer that is unique among the objects that exist at the same time. In CPython it happens to be the memory address, but that is an implementation detail, and an id can be reused after its object is gone. Compare ids; don't print them.type(obj)returns the object's class.typeis itself a class, not a method.isinstance(obj, cls)asks "is this object an instance ofcls, or of a subclass of it?". It accepts a tuple of classes. Prefer it totype(x) == intwhen you check types, because it respects subclasses.
Pitfall. The last line printed
True:boolis a subclass ofint, soisinstance(True, int)is true. If your code must reject booleans where it expects numbers, check forboolfirst.
Equality and identity: == versus is
a == basks whether the values are equal. Each type decides what "equal" means.a is basks whether both names refer to the same object.
Use == for comparing values, which is almost always what you want. Use is only to check for a singleton, an object of which only one exists. The common case is None: write if result is None:.
Equal values are not guaranteed to be the same object. Sometimes they are, which is exactly what makes is dangerous for value comparisons:
The third line is the trap. CPython keeps one shared object for each small integer (from −5 to 1024 in 3.15), and it interns some strings, keeping a single shared copy of, for example, short literals that look like names. A quick test with small values seems to show that is compares values; with larger or computed values it doesn't. The exact rules are an implementation detail that has changed between versions, so never rely on them.
Pitfall. Never use
isto compare numbers, strings or other values. Python even warns you:x is 7with a literal produces aSyntaxWarning. Use==, and keepisforNone(and the rare sentinel object you create yourself).
Mutable and immutable types
An object is mutable if its value can change after it is created, and immutable if it can't. An operation that seems to change an immutable object actually creates a new one. The table also shows which types are hashable, which you will need for dictionary keys and sets (the section on hashability below explains it).
| Type | Example literal | Mutable? | Hashable? |
|---|---|---|---|
int | 42, 0xFF, 1_000_000 | no | yes |
float | 3.14, 1e-3 | no | yes |
complex | 2+3j | no | yes |
bool | True, False | no | yes |
NoneType | None | no | yes |
str | "text", 'text' | no | yes |
bytes | b"raw" | no | yes |
tuple | (1, "a"), (1,) | no | only if all its items are hashable |
frozenset | frozenset({1, 2}) | no | yes |
range | range(10) | no | yes |
list | [1, 2] | yes | no |
dict | {"k": 1} | yes | no |
set | {1, 2} (set() when empty) | yes | no |
bytearray | bytearray(b"raw") | yes | no |
A set is mutable, but each of its elements must be hashable. The standard library also offers array.array, a mutable sequence of numbers of one type; it isn't a built in type, so you import it from the array module.
You can see the difference with id. Adding to a string builds a new string and rebinds the name; adding to a list with += changes the same list:
Trying to change part of an immutable object is an error:
The error message is:
An immutable container can still hold mutable objects. The tuple below can't be changed to refer to different objects, but the list inside it can change:
Numbers
Integers
int has no fixed size: an integer can be as large as your memory allows, so there is no overflow. Underscores make long literals readable, and prefixes select a base:
There is one limit, added in Python 3.11 to stop denial of service attacks: converting an integer with more than 4,300 decimal digits to or from a string raises ValueError. Arithmetic on huge numbers is fine, and power of two bases such as hexadecimal aren't limited. If you really need longer conversions, raise the limit with sys.set_int_max_str_digits():
Floating point numbers
float is a 64 bit binary floating point number (IEEE 754 double precision), good for about 15 to 17 significant decimal digits. Most decimal fractions, such as 0.1, can't be stored exactly in binary, so tiny errors appear:
This isn't a Python bug; every language that uses binary floating point behaves the same way. Two habits follow:
- Compare floats with
math.isclose(a, b)instead of==. By default it allows a relative difference of about one part in a billion; passabs_tol=when comparing with values near zero. - Round only when you display a value, for example with an f-string such as
f"{total:.2f}"(see Strings and text formatting).
float also has the special values float("inf") and float("nan") ("not a number"). A NaN is not equal to anything, not even itself.
Decimal and Fraction for exact arithmetic
For money, use decimal.Decimal, which stores decimal digits exactly. Always create it from a string or an integer: a float has already lost precision before Decimal sees it. fractions.Fraction stores exact ratios.
quantize(Decimal("0.01"), rounding=ROUND_HALF_UP) rounds to cents the way most people expect. The line Decimal(0.1) shows the exact binary value that the float 0.1 really holds.
Tip. Another common approach is to store money as an integer number of cents and only format it as pounds or euros for display.
Complex numbers
Python has complex numbers built in, with j as the imaginary unit: for z = 3 + 4j, z.real is 3.0, z.imag is 4.0 and abs(z) is 5.0. The cmath module has complex versions of the functions in math. You won't need complex numbers again in this course.
Booleans and None
bool
bool has exactly two objects, True and False. It is a subclass of int, with True == 1 and False == 0, which is occasionally handy:
Truthiness
Any object can be used where Python expects a condition, for example in an if. Each object is then treated as truthy or falsy, and bool(obj) tells you which:
| Falsy | Truthy |
|---|---|
False, None | True |
zero: 0, 0.0, 0j, Decimal(0), Fraction(0) | any nonzero number, including -1 |
empty containers: "", b"", [], (), {}, set(), range(0) | any nonempty container, including "0", " " and [0] |
So if items: is the idiomatic way to ask "is this list nonempty?". Note that {} is an empty dict; an empty set is written set().
Pitfall.
bool("False")isTrue: it's a nonempty string. To turn user text into a boolean, compare it explicitly, for exampletext.strip().lower() in {"yes", "y", "true"}.
None
None is the single object of type NoneType. It means "no value here": a result that doesn't exist yet, an optional setting that wasn't given. A function that ends without a return statement returns None, and so do methods that change an object in place:
The second example is a classic bug: numbers = numbers.sort() throws your list away and keeps None. Use sorted(numbers) when you want a new list.
Test for None with is None or is not None. Don't write if not value: when 0 or "" are valid values, because they are falsy too.
Converting between types
Calling a type converts a value to that type, when a conversion makes sense:
| Call | Result | Notes |
|---|---|---|
int("42"), int(" 42\n") | 42 | surrounding whitespace is ignored |
int("1_000") | 1000 | underscores between digits are allowed |
int("ff", 16), int("0b101", 0) | 255, 5 | base 0 reads the prefix: 0b, 0o or 0x |
int(4.9), int(-4.9) | 4, -4 | truncates towards zero; use round() or math.floor() otherwise |
int("4.2") | ValueError | parse with float() first if you expect decimals |
float("2.5e3") | 2500.0 | also accepts "inf" and "nan" |
str(3.0), str(None) | '3.0', 'None' | every object can be turned into a string |
bool("False") | True | nonempty string; see the pitfall above |
list("abc") | ['a', 'b', 'c'] | any iterable can feed list, tuple or set |
tuple([1, 2]), set([1, 1, 2]) | (1, 2), {1, 2} | set drops duplicates |
dict([("a", 1), ("b", 2)]) | {'a': 1, 'b': 2} | from key and value pairs |
bytes(5) | b'\x00\x00\x00\x00\x00' | an integer means "this many zero bytes" |
"héllo".encode("utf-8") | b'h\xc3\xa9llo' | text to bytes; covered in Text, bytes and Unicode |
Text typed by a user or read from a file is always a str, so converting it is a daily task. Invalid text raises ValueError, which you can catch with try/except; the Errors and exceptions unit covers this properly:
Naming things
The rules
A name must:
- start with a letter or an underscore, followed by letters, digits or underscores. "Letter" includes non English letters (PEP 3131), so
größeis a valid name, although most projects stick to ASCII; - not be a keyword, a word reserved by the language.
Names are case sensitive: total, Total and TOTAL are three different names. The keyword module lists the reserved words:
Soft keywords are special only in certain positions, so they are still valid names: match = 3 works. That output comes from 3.15, which added lazy for explicit lazy imports (PEP 810); 3.13 and 3.14 list the other four.
Conventions (PEP 8)
PEP 8, the style guide for Python code, sets these naming conventions:
| Kind of name | Style | Examples |
|---|---|---|
| variables, functions, methods, modules | snake_case | total_price, load_config, file_utils |
| constants | UPPER_SNAKE_CASE | MAX_RETRIES, DEFAULT_TIMEOUT |
| classes, exceptions, type aliases | CapWords | ShoppingCart, ConfigError |
| internal ("please don't use from outside") | leading underscore | _cache, _parse_line |
| a value you deliberately ignore | _ | for _ in range(3): |
Choose names that say what a value means: elapsed_seconds rather than t, customers rather than data2. Avoid l, O and I as single letter names; they look like digits in many fonts.
Python has no true constants. MAX_RETRIES = 3 is a normal name; the capitals tell readers not to rebind it. Type checkers can enforce that if you annotate it with typing.Final.
Pitfall. Don't reuse the names of built in functions and types such as
list,dict,str,id,type,input,sumormaxfor your own values. Afterlist = [1, 2], a laterlist("abc")fails withTypeError: 'list' object is not callable. Useitems,namesortotalsinstead.
Assignment forms
Python has several forms of assignment. All of them bind names; none of them copy objects.
- On the right,
10, 20builds a tuple; on the left, the names receive its items in order. That's why the swap works: the right side is evaluated completely before any name is rebound. - In unpacking, one target may carry a
*. It collects all the items not taken by the other targets, always into a list, even when the source is a tuple or a string. - The number of targets must match the number of items. Otherwise you get
ValueError: too many values to unpack (expected 2, got 3)ornot enough values to unpack. Python 3.13 leaves out thegot 3part.
Chained assignment a = b = value binds both names to the same object. That is fine for immutable values, and a trap for mutable ones:
del name removes a name, not an object. The object lives on while other names (or containers) still refer to it; Python frees it automatically once nothing does:
Augmented assignment such as count += 1 is covered in Operators and expressions, including the important detail that += changes a list in place.
Aliasing and copying
When two names refer to the same mutable object, they are aliases. Aliasing is often what you want, for example when a function changes a list you pass to it. When it isn't, make a copy, and choose the right depth:
- A shallow copy creates a new outer container but puts the same inner objects in it.
items.copy(),items[:],list(items),dict(mapping)andcopy.copy(obj)all make shallow copies. - A deep copy, made with
copy.deepcopy(obj), copies the inner objects too, recursively.
The shallow copy didn't get [5, 6], because it has its own outer list. It did see the 99, because it shares inner list A with grid. The deep copy shares nothing.
Pitfall.
[[0] * 3] * 3looks like a 3×3 grid, but it is one row repeated three times:*copies references, not objects. Afterboard[0][0] = 1, every row starts with 1. Build independent rows with a loop or a comprehension:[[0] * 3 for _ in range(3)](see Collections and comprehensions).
Hashability
A hash is an integer computed from an object's value, which lets dictionaries and sets find items quickly. An object is hashable if its hash never changes during its lifetime and equal objects have equal hashes. Only hashable objects can be dictionary keys or set elements.
The immutable built in types are hashable; the mutable containers are not, because changing them would change their hash. A tuple is hashable only if everything inside it is:
The first error message has this wording since Python 3.14. On 3.13 both lines read unhashable type: 'list'.
Because equal objects must have equal hashes, 1, 1.0 and True count as the same dictionary key: {1: "a", True: "b"} ends up as {1: 'b'}.
Note. The hash of a string is randomised each time Python starts, to make certain attacks harder. Never store hashes or rely on their values between runs; this is also why the display order of a set of strings can change from run to run.
Variable annotations: a preview
You can annotate a name with the type you intend it to hold. This is the same type hint syntax you saw in the Introduction to Python unit:
list[str] means "a list of strings", and float | None means "a float or None". Python doesn't enforce any of this, but a type checker such as mypy or pyright would flag count = "zero". Annotations are most useful on functions, which the Functions unit covers.
Check yourself
- What are the three properties every object has, and which of them can change?
- After
a = [1]andb = a, how many lists exist? What doesb.append(2)do toa? - When should you use
isinstead of==? - Why can
int("7") is 7beTruewhileint("1000000") is 10 ** 6isFalse? - A tuple is immutable. Why can
pair = (1, [2])still change, and ispairhashable? - Why is
0.1 + 0.2 == 0.3false, and what should you use instead for comparisons and for money? - What does
first, *rest = "abc"bind, and what type isrest? - What is the difference between
copy.copy(grid)andcopy.deepcopy(grid)for a list of lists?
Suggested answers
- Identity, type and value. Identity and type never change; the value can change only if the type is mutable.
- One list with two names.
asees the change: both print[1, 2]. - When you check for a singleton, above all
x is Noneorx is not None. Compare values with==. - CPython caches small integers, so every
7is the same object; large integers created at run time are separate objects. The result depends on implementation details, which is whyismustn't be used for values. - The tuple can't be changed to refer to other objects, but the list inside it is mutable and can change. Because it contains a list,
hash(pair)raisesTypeError: it isn't hashable. - 0.1, 0.2 and 0.3 can't be stored exactly in binary floating point, so the sum is
0.30000000000000004. Compare floats withmath.isclose; useDecimalcreated from strings (or integer cents) for money. firstis'a'andrestis['b', 'c']. A starred target always produces a list.- Both create a new outer list. The shallow copy shares the inner lists with
grid, so changes to them show up in both; the deep copy copies the inner lists too and shares nothing.
Exercises
These exercises are optional practice. Pick the ones that help you.
Some solutions use try and except, which Errors and exceptions explains in full.
- Same or equal? Write
relation(a, b)that returns"same object"ifaandbare the same object,"equal"if they are different but equal objects, and"different"otherwise. Forx = [1]:relation(x, x)→"same object",relation(x, [1])→"equal",relation(x, [2])→"different". - Hashable or not? Write
is_hashable(value)that returnsTrueifhash(value)succeeds andFalseif it raisesTypeError. Check it with42,"text",(1, 2),(1, [2]),[1],{}andfrozenset({1}). - Fair bill split. Write
split_bill(total, people)wheretotalis aDecimalsuch asDecimal("100.00"). Return a list ofpeopleshares in whole cents that differ by at most one cent and add up exactly tototal, with the larger shares first.split_bill(Decimal("100.00"), 3)→[Decimal('33.34'), Decimal('33.33'), Decimal('33.33')]. RaiseValueErrorifpeopleis less than 1. - Parse or default. Write
to_int(text, default=None)that returnsint(text)when the text is a valid integer (surrounding spaces allowed) anddefaultotherwise.to_int(" 12 ")→12,to_int("12.5")→None,to_int("", 0)→0. - Rotate with unpacking. Write
rotate_left(values)that returns a tuple with the first item moved to the end, using starred unpacking.rotate_left((1, 2, 3))→(2, 3, 1),rotate_left("ab")→('b', 'a'), and an empty input gives().
Solutions
1. Same or equal?
The identity check comes first: the same object is always equal to itself (apart from the odd case of NaN), so testing == first would hide it.
2. Hashable or not?
3. Fair bill split.
Working in whole cents avoids any rounding: the leftover cents from divmod go to the first people, one each.
4. Parse or default.
int() already ignores surrounding whitespace, so no strip() is needed.
5. Rotate with unpacking.
(*rest, first) unpacks the list into a new tuple. Without the emptiness check, first, *rest = () would raise ValueError because there is nothing to bind to first.