Python Data Types
Every type, every line explained
A complete, runnable reference for Python's built-in data types. Every code block includes a numbered line-by-line breakdown so nothing feels like magic — and a Run button so you can test everything instantly in your browser. Includes three practice exercises and a full quick-reference table.
# A first look at Python's built-in types age: int = 25 height: float = 1.75 name: str = "Ana" is_student: bool = True hobbies: list = ["reading", "coding"] coordinates: tuple = (40.71, -74.01) scores: dict = {"math": 95, "science": 88} unique_tags: set = {"python", "tutorial"} nothing_here: NoneType = None for value in (age, height, name, is_student, hobbies, coordinates, scores, unique_tags, nothing_here): print(f"{value!r:>24} → {type(value).__name__}")
📚 What This Lesson Covers
Eleven sections, each with runnable code and a numbered explanation of every line
🧬 What Is a Type, Really?
Every value in Python has a type that determines what it can do
A type is the blueprint for a value. It answers three questions: what data does this value hold, what operations can I perform on it, and how much memory does it need? In Python you almost never declare a type explicitly — the interpreter infers it from the value you assign. But knowing exactly which type you're working with is what separates guessing from debugging.
🔍 Checking Any Value's Type
Use type() to get the exact type object, and isinstance() to test membership in a type hierarchy. The latter is safer when subclasses are involved.
values = [42, 3.14, "hello", True, [1, 2], (1, 2), {"a": 1}, {1, 2}, None] for v in values: print(f"{str(v):<14} type={type(v).__name__:<10} isinstance(int)? {isinstance(v, int)}")
- 1
values = [...]— a single list holding one example of each core built-in type:int,float,str,bool,list,tuple,dict,set, andNone. - 2
for v in values:— iterates over each value, binding the loop variablevto the current item. No index needed; Python'sforis a for‑each loop. - 3
print(f"...")— an f-string containing three expressions inside{}braces:str(v)renders the value as readable text,type(v).__name__extracts the type's name as a string (e.g."int","list"), andisinstance(v, int)returnsTrueonly for integer values. - 4
<14and:<10inside the f-string are format specifiers — left-align each field in 14 and 10 characters respectively so the columns line up.
True reports isinstance(int)? True. In Python, bool is a subclass of int — True == 1 and False == 0. That's a deliberate design choice, not a bug.🔢 Numeric Types — int, float, complex
Whole numbers, decimal numbers, and complex numbers — each with different precision and range
🔢 Integers — Arbitrary Precision
Python integers have no fixed size. They grow as large as your memory allows — no overflow, no wrapping. You can write them in decimal, binary, octal, or hexadecimal.
a = 42 b = 1_000_000 c = 0b101010 d = 0o52 e = 0x2A print(f"a={a} b={b} c={c} d={d} e={e}") print(f"Same value? {a == c == d == e}") big = 2 ** 1000 print(f"2^1000 has {len(str(big))} digits")
- 1
a = 42— a decimal integer literal. The variableais bound to theintobject representing forty-two. - 2
b = 1_000_000— underscores are visual separators only. The value is exactly one million; Python ignores the underscores. - 3
c = 0b101010— a binary literal.0bprefix means base 2.101010₂ = 42₁₀. - 4
d = 0o52— an octal literal.0oprefix means base 8.52₈ = 42₁₀. - 5
e = 0x2A— a hexadecimal literal.0xprefix means base 16.2A₁₆ = 42₁₀. - 6
print(...)prints all five values; each prints as42because Python normalises numeric literals to their numeric value regardless of how they were written. - 7
a == c == d == e— a chained comparison. Python evaluates this asa == c and c == d and d == e, which isTruebecause all four names refer to the same numeric value. - 8
big = 2 ** 1000— exponentiation operator**. Computes 2 to the 1000th power — an enormous integer with over 300 digits. Python handles it without overflow. - 9
len(str(big))converts the huge integer to its string representation, then counts the characters — revealing how many digits it has. The result: 302 digits.
int is arbitrary‑precision, 2 ** 1000 is computed exactly. In most other languages, this would overflow a fixed 64‑bit register.🔣 Floating-Point Numbers — Approximate, Not Exact
A float is a 64‑bit IEEE‑754 double-precision number. It can represent very large and very small numbers — but not every decimal fraction exactly. 0.1 + 0.2 is not exactly 0.3. That's a property of the binary representation, not a Python bug.
price = 19.99 tax_rate = 0.08 total = price * (1 + tax_rate) print(f"Total: {total}") print(f"Rounded: {round(total, 2)}") print(f"0.1 + 0.2 = {0.1 + 0.2}") print(f"Equal to 0.3? {0.1 + 0.2 == 0.3}") import math print(f"Approx equal? {math.isclose(0.1 + 0.2, 0.3)}") from decimal import Decimal exact_total = Decimal("19.99") * (Decimal(1) + Decimal("0.08")) print(f"Exact with Decimal: {exact_total}")
- 1
price = 19.99— a float literal. Stored in binary as the closest representable double to 19.99. - 2
tax_rate = 0.08— another float. Like all decimal fractions, 0.08 cannot be represented exactly in binary. - 3
total = price * (1 + tax_rate)— multiplication and addition. The result carries tiny floating-point error accumulated through both operations. - 4The first
printshows the raw float — likely21.5892with a long tail of unexpected digits. - 5
round(total, 2)rounds to two decimal places. Useful for display, but don't rely on rounding to fix all floating-point issues in financial calculations. - 6
0.1 + 0.2yields0.30000000000000004— the classic demonstration. Neither 0.1 nor 0.2 is exactly representable in binary. - 7
0.1 + 0.2 == 0.3returnsFalsebecause the two values differ by about 5.5e‑17 — small, but not zero. - 8
math.isclose()is the correct way to compare floats. It checks whether the two numbers are within a small relative tolerance — default1e‑09. - 9
Decimal("19.99")— thedecimalmodule provides exact decimal arithmetic. Note the strings:Decimal("19.99")is exact, whileDecimal(19.99)would inherit the float's imprecision.
Decimal or store amounts as integer cents. Never use float for financial calculations you care about.🌀 Complex Numbers — Real + Imaginary
Python has built-in support for complex numbers using j as the imaginary unit. Used in signal processing, electrical engineering, and some mathematical computations.
z = 3 + 4j print(f"z = {z}") print(f"Real part: {z.real}") print(f"Imaginary part: {z.imag}") print(f"Magnitude: {abs(z)}") print(f"Conjugate: {z.conjugate()}")
- 1
z = 3 + 4j— a complex number literal.jis the imaginary unit (√‑1). Python writes it as(3+4j). - 2
z.real— the real component,3.0as a float. - 3
z.imag— the imaginary component,4.0. - 4
abs(z)— the magnitude (modulus) of the complex number: √(3² + 4²) = 5.0. - 5
z.conjugate()— the complex conjugate:3 - 4j.
📝 Text — the str Type
Immutable sequences of Unicode characters — Python's most flexible and most used type
✍️ Creating and Inspecting Strings
Strings are created with single quotes, double quotes, or triple quotes (for multi-line). They are immutable — every method returns a new string rather than modifying the original.
name = "Ana" greeting = 'Hello' paragraph = """Line one Line two""" print(f"Length: {len(name)}") print(f"First char: {name[0]}") print(f"Last char: {name[-1]}") print(f"Reversed: {name[::-1]}") print(f"Uppercase: {name.upper()}") print(f"Original unchanged: {name}") print(f"Split: {'Hello World'.split()}") print(f"Join: {', '.join(['a', 'b', 'c'])}")
- 1
name = "Ana"— a string literal. Thestrobject is created and bound toname. - 2
greeting = 'Hello'— single quotes work exactly the same as double quotes. The choice is style, not substance. - 3
paragraph = """Line one\nLine two"""— triple-quoted string spans multiple lines. The newline is included in the string. - 4
len(name)returns 3 — the number of characters in the string. - 5
name[0]— indexing. Python uses zero‑based indexing, so the first character is at index 0:'A'. - 6
name[-1]— negative indexing counts from the end. The last character is at index ‑1:'a'. - 7
name[::-1]— slicing with step ‑1 reverses the string:'anA'. - 8
name.upper()returns'ANA'— a new string. The originalnameis untouched. - 9The line
Original unchanged: Anaconfirms that string methods never mutate in place. - 10
"Hello World".split()returns['Hello', 'World']— splitting on whitespace by default. - 11
", ".join(...)joins a list of strings into one string with", "between each element.
s[0] = "B" raises TypeError. To "change" a string, you must create a new one — like s = "B" + s[1:].🎨 f-strings — The Modern Way to Format
Introduced in Python 3.6, f-strings are the recommended way to embed values inside strings. They are evaluated at runtime, support any expression, and offer rich format specifications.
name = "Ana" score = 95.678 items = 1234567 print(f"Hello, {name}!") print(f"Score: {score:.1f}") print(f"Score: {score:.2f}") print(f"Padded: {name:>8}|") print(f"Centred: {name:^8}|") print(f"Percent: {0.856:.1%}") print(f"Comma: {items:,}") print(f"Debug: {name=} {score=}")
- 1
f"Hello, {name}!"— thefprefix marks an f-string. Anything inside{}is evaluated as a Python expression and inserted. - 2
{score:.1f}— format spec.1fmeans "fixed-point, one decimal place". Result:95.7. - 3
{score:.2f}— two decimal places:95.68. - 4
{name:>8}— right-aligns within 8 characters. With"Ana", result is" Ana". - 5
{name:^8}— centres within 8 characters:" Ana ". - 6
{0.856:.1%}— percentage format:85.6%. - 7
{items:,}— comma as thousands separator:1,234,567. - 8
{name=}— the=debug syntax (Python 3.8+) prints both the expression and its value:name='Ana' score=95.678.
✅ Boolean — True and False
The smallest type, but behind every comparison, condition, and logical expression
🧠 Truthiness — What Counts as True?
Every Python value can be tested in a boolean context. Rather than memorising rules, think about which values are empty or zero — those are falsy. Everything else is truthy.
falsy_values = [False, None, 0, 0.0, "", [], (), {}, set()] for v in falsy_values: print(f"{v!r:<10} → bool is {bool(v)}") truthy_values = [True, 1, -1, 3.14, "0", [0], (0,), {"key": None}] for v in truthy_values: print(f"{v!r:<10} → bool is {bool(v)}")
- 1A list of values that Python considers falsy —
False,None, zero of any numeric type, and any empty container. - 2–3Loops over each falsy candidate and prints its
bool()conversion. All printFalse. - 5A list of values that Python considers truthy — even
"0"(the non‑empty string) and[0](a list containing zero). - 7–8Loops and prints — all print
True. A container is truthy if it contains anything, regardless of what that thing is.
if len(my_list) > 0: — write if my_list:. Python's truthiness rules let you test for "non-empty" directly. It's idiomatic, readable, and works for every container type.⚡ Short-Circuit Evaluation
The operators and and or stop evaluating as soon as the outcome is determined. This is not just an optimisation — it's often a deliberate guard against errors.
a = 0 b = 5 print(a and b) print(a or b) print(not a) name = "" display = name or "Anonymous" print(display) safe = b != 0 and (100 / b) print(safe)
- 1–2Two integers:
a = 0(falsy) andb = 5(truthy). - 4
a and b— sinceais falsy,andshort‑circuits and returnsaitself:0. - 5
a or b— sinceais falsy,orreturnsb:5. - 6
not a—ais0, sonot aisTrue. - 8–9
name or "Anonymous"— a standard Python idiom for supplying a default when a value is falsy. Sincenameis"", the result is"Anonymous". - 11–12
b != 0 and (100 / b)— ifbwere zero,andwould short‑circuit and the division would never run. This pattern preventsZeroDivisionError.
📦 Collection Types — list, tuple, dict, set
Four data structures for organising many values — each with different trade‑offs
📋 list — Ordered, Mutable, Allows Duplicates
fruits = ["apple", "banana", "cherry"] fruits.append("date") fruits.insert(1, "blueberry") fruits.remove("banana") popped = fruits.pop() print(f"Final list: {fruits}") print(f"Popped: {popped}") print(f"First two: {fruits[:2]}") print(f"Squares: {[x**2 for x in range(1, 6)]}")
- 1A list literal with three strings. Lists preserve insertion order and allow duplicate values.
- 3
.append("date")adds one element to the end — O(1) amortised. - 4
.insert(1, "blueberry")inserts at index 1, shifting everything right — O(n). - 5
.remove("banana")removes the first occurrence of that value. - 6
.pop()removes and returns the last element —"date". - 8–9Prints the final list and the popped value.
- 10
fruits[:2]— slicing from start to (but not including) index 2. - 11A list comprehension:
[x**2 for x in range(1, 6)]creates[1, 4, 9, 16, 25].
🎯 tuple — Ordered, Immutable
point = (3, 7) rgb = (255, 128, 0) x, y = point print(f"x={x}, y={y}") first, *middle, last = rgb print(f"first={first} middle={middle} last={last}") single = (42,) print(f"Type: {type(single).__name__}, length: {len(single)}")
- 1
point = (3, 7)— a tuple literal. Once created, its elements cannot be changed. - 2
rgb = (255, 128, 0)— a three‑element tuple. Order is significant. - 4
x, y = point— tuple unpacking. Values are assigned left‑to‑right from the tuple. - 7
first, *middle, last = rgb— starred unpacking:first = 255,middle = [128],last = 0. - 10
single = (42,)— the trailing comma is crucial. Without it,(42)is just the integer 42, not a tuple.
📖 dict — Key-Value Pairs, O(1) Lookup
student = {"name": "Ana", "age": 25, "scores": [95, 88]}
student["grade"] = "A"
print(student.get("email", "not provided"))
for key, value in student.items():
print(f"{key}: {value}")
keys = list(student.keys())
values = list(student.values())
print(f"Keys: {keys}")
print(f"Values: {values}")- 1A dict literal with three key‑value pairs. Keys must be hashable — strings, numbers, or tuples.
- 3
student["grade"] = "A"— assigns a new key‑value pair. If the key existed, it would be overwritten. - 4
.get("email", "not provided")— returns"not provided"instead of raisingKeyError. The second argument is the default. - 6–7
.items()returns(key, value)tuples, unpacked intokeyandvaluefor each iteration. - 9–10
.keys()and.values()return view objects;list()materialises them into lists.
if "key" in d: v = d["key"] when you can write v = d.get("key", default). The second version is shorter, safer, and does a single lookup.🔵 set — Unordered, Unique
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
print(f"Union: {a | b}")
print(f"Intersection: {a & b}")
print(f"Difference: {a - b}")
print(f"Symmetric difference: {a ^ b}")
print(f"Is 3 in a? {3 in a}")
print(f"Deduplicated: {set([1, 2, 2, 3, 3, 3])}")- 1–2Two sets. Order is not guaranteed and duplicates are automatically removed.
- 4
a | b— union: all elements from either set. Result:{1, 2, 3, 4, 5, 6}. - 5
a & b— intersection: elements present in both. Result:{3, 4}. - 6
a - b— difference: elements inabut notb. Result:{1, 2}. - 7
a ^ b— symmetric difference: elements in either set but not both. Result:{1, 2, 5, 6}. - 8
3 in a— membership test. O(1) time regardless of set size. - 9
set([1, 2, 2, 3, 3, 3])removes duplicates. The result is{1, 2, 3}.
🚫 None — The Absence of a Value
NoneType has exactly one value: None. It is not zero, not an empty string, not False — it's the absence of a value.
🚫 None and is None
result = None def find_user(user_id): users = {1: "Ana", 2: "Bob"} return users.get(user_id) found = find_user(3) print(f"Found: {found}") print(f"Is it None? {found is None}") print(f"Type: {type(None).__name__}") print(f"Bool of None: {bool(None)}")
- 1
result = None—Noneis a keyword-like literal.NoneTypeis a singleton: only oneNoneobject exists. - 3–5
find_userreturnsusers.get(user_id). Since key 3 doesn't exist,.get()returnsNone. - 7
foundreceivesNone. - 8
found is None— the recommended way to compare toNone. Useis, not==; it's faster and not overridable. - 9
type(None).__name__reports"NoneType". - 10
bool(None)isFalse—Noneis falsy in conditional contexts.
x is None, never x == None. Use if x is not None: to check for existence — not if x:, which is also False for 0, "", and empty containers.🔁 Type Conversion — Casting Between Types
Explicit conversions are almost always safer than implicit ones
🔁 Explicit Casting with int(), float(), str(), list()
print(int("42")) print(int(3.99)) print(float(7)) print(str(100)) print(list("abc")) print(tuple([1, 2, 3])) print(set([1, 2, 2, 3])) try: int("hello") except ValueError as e: print(f"Cannot convert: {e}")
- 1
int("42")parses the string into the integer 42. Leading/trailing whitespace is ignored. - 2
int(3.99)truncates the decimal portion — result is3, not4. It does not round. - 3
float(7)converts the integer to7.0. - 4
str(100)produces"100". - 5
list("abc")iterates the string, producing['a', 'b', 'c']. - 6
tuple([1, 2, 3])converts a list to a tuple. - 7
set(...)removes duplicates — result is{1, 2, 3}. - 9–11
int("hello")raisesValueErrorbecause the string is not a valid integer literal. Always wrap user input in try/except.
🔒 Mutable vs Immutable — A Distinction That Matters
Some types can be changed in place; others cannot. This difference drives real bugs when misunderstood
⚡ The Assignment Trap
Assignment in Python binds a name to an object. It does not copy the object. For mutable objects like lists, two names can point to the same data.
# Immutable — reassignment creates a new object a = 10 b = a a = 20 print(f"a={a}, b={b}") # Mutable — alias shares the same object list_a = [1, 2, 3] list_b = list_a list_a.append(4) print(f"list_a={list_a}, list_b={list_b}") # Safe copy — independent list_c = list_a.copy() list_a.append(99) print(f"list_a={list_a}, list_c={list_c}")
- 2–5
a = 10; b = a—bpoints to the same integer object. Thena = 20rebindsato a new object;bstill points to 10. Integers are immutable, so this is safe. - 8–11
list_b = list_a— both names point to the same list.list_a.append(4)modifies the shared object, solist_bsees the change too. - 14–16
list_c = list_a.copy()creates a new independent list. Appending tolist_ahas no effect onlist_c.
Immutable Types
int, float, complex, str, bool, tuple, frozenset, NoneType, bytes — safe to share, usable as dict keys.
Mutable Types
list, dict, set, bytearray, most custom class instances — changes propagate through every reference.
🏋️ Practice — Check Your Understanding
Three exercises with solutions. Try each before expanding the answer.
Exercise 1 — Identify the Types
What are the types of each value? Predict before running.
values = [42, 3.0, "42", True, None, [42], (42,), {42}, {"answer": 42}] for v in values: print(f"{v!r:<16} → {type(v).__name__}")
Show solution
int, float, str, bool, NoneType, list, tuple, set, dict — in that order.
Exercise 2 — Fix the Bug
Why does this loop skip the "banana"? Fix it without using a comprehension.
fruits = ["apple", "banana", "cherry"] for fruit in fruits: if fruit.startswith("b"): fruits.remove(fruit) print(fruits)
Show solution
Removing from a list while iterating shifts indices, so the next element is skipped. Iterate over a copy: for fruit in fruits[:]: — or build a new list. The slice fruits[:] creates a shallow copy so the loop and the mutation don't interfere.
Exercise 3 — Safe Type Conversion
Write a function safe_int(value, default=0) that returns an integer or a default if conversion fails. Test it with "42", "hello", and 3.99.
Show solution
def safe_int(value, default=0): try: return int(value) except (ValueError, TypeError): return default print(safe_int("42")) print(safe_int("hello", -1)) print(safe_int(3.99))
Catching both ValueError (bad string) and TypeError (unsupported type) makes the helper genuinely safe.
❓ Frequently Asked Questions
Common questions about Python's data types, answered directly
Because float values are stored in binary (IEEE 754 double precision), and the decimal fractions 0.1 and 0.2 have no exact binary representation — just as 1/3 has no exact decimal representation. The sum carries a tiny error (~5.5e‑17). For comparisons, use math.isclose(a, b); for money, use Decimal. This is a property of floating-point arithmetic, not a Python bug.
Use a tuple when the collection is fixed and should not change — coordinates, RGB values, database records, or anything you need to use as a dictionary key (tuples are hashable; lists are not). Use a list when you need to add, remove, or reorder items. Tuples are also slightly faster and lighter in memory.
Yes. True and False are instances of bool, and bool inherits from int. Numerically, True == 1 and False == 0. This is why isinstance(True, int) returns True, and why you can use booleans in arithmetic (though doing so is rarely good style).
Use if x is None: — not if x == None: and not if not x:. The is operator checks identity against the single None object and cannot be overridden by a custom __eq__. Writing if not x: also catches 0, "", [], and False, which is usually not what you mean when checking for None.
📋 Quick-Reference Table
Every built-in type at a glance — bookmark this page for quick revision
| Type | Example | Mutable? | Ordered? | Duplicates? |
|---|---|---|---|---|
int | 42 | No | N/A | N/A |
float | 3.14 | No | N/A | N/A |
str | "hello" | No | Yes | Yes |
bool | True | No | N/A | N/A |
list | [1, 2, 3] | Yes | Yes | Yes |
tuple | (1, 2, 3) | No | Yes | Yes |
dict | {"a": 1} | Yes | Yes | No (keys) |
set | {1, 2, 3} | Yes | No | No |
NoneType | None | No | N/A | N/A |
Educational Use Disclaimer
This page is part of Poetic Bytes, a free educational resource for learning Python programming. The code examples are provided for instruction and demonstration. They are not production-audited and should not be used as-is in security-sensitive, financial, or safety-critical systems without independent review. See our full disclaimer and terms of use.
📬 Python, weekly
Short lessons, real code, and ecosystem news — sent about once a week
🗣️ Learner Voices
No comments yet — your voice starts the conversation.