Variables and Data Types
Learn how Python variables are names bound to objects, how int, float, str, bool and None literals differ, and when exact arithmetic demands Decimal and f-strings
A coffee stand program prints its receipt, and the cashier stops laughing:
>>> tea_price = 0.10
>>> croissant_price = 0.20
>>> print("Total:", tea_price + croissant_price)
Total: 0.30000000000000004Tea and a croissant, and the register announces a total with fourteen extra digits. The customer sees a screen that cannot add two numbers written as 0.10 and 0.20. Nothing in the program is broken. Python did exactly what its design says to do, and every digit of that long number is there for a reason that you will be able to explain by the end of this lesson.
The receipt sits at the crossing point of three ideas that this lesson builds
from the ground up. First, names such as tea_price point at values; they are
not boxes that hold the values. Second, values arrive in kinds, such as whole
numbers, numbers with a dot, text, answers and an explicit empty slot. Third,
each kind has rules, so money, measurements, counts and text each need the
right tool.
This lesson is the third stop of the course. By the end you will bind names, read any written value and name its kind, convert between kinds safely, dodge the classic floating point trap, and write strings that interpolate values on the fly. Every claim here is checkable in a Python 3.15 REPL, and there are interactive checks along the way.
Key terms
- A single piece of data living in memory, such as the number 3 or the text hello, that a name can point at.
- A name that your program uses to refer to an object. The name is a sticky note, not a box that stores the value itself.
- A value written directly into source code, such as 42, 3.14, text in quotes, True or None.
- The kind of value an object carries, such as int for whole numbers or str for text.
- A note next to a name or a function that documents the expected kind of value for readers and checker tools.
- A string prefixed with the letter f that inserts values into text, such as f"total: {total}".
- Unchangeable after creation. Python strings are immutable, so text operations hand back a new string instead of editing one in place.
- Building a value of one kind from another with built-in functions such as int, float and str.
Object
Variable
Literal
Data type
Type annotation
f-string
Immutable
Type conversion
1. Names and Objects: the Sticky Note Model
Start with the example from the receipt. The line tea_price = 0.10 does two
things. Python first builds one object holding the value 0.1, and then binds
the name tea_price to that object. The name is a label you can use to find
the object again, and the type belongs to the object, not to the name.
tea_price = 0.10 # one float object, one name pointing at it
quantity = 3 # a second object, a second name
quantity = 4 # the same name, now bound to a different objectTwo properties of the model explain almost everything that confuses beginners about assignment:
- Rebinding is normal. Assigning to a name again does not edit the old object; it turns the sticky note toward a new one.
- The type lives on the object. The name
quantitynever had a type of its own. It pointed at an int object, and later a name likequantitycould point at text instead.
Python permits a name to hold an int now and a str later. The diagram below
follows one name, count, as a program rebinds it from one kind of object to
another. Every arrow is an ordinary assignment, and every new state is a
different object existing in memory.
Description
A state diagram for the section 1. Names and Objects: the Sticky Note Model, showing the states and transitions described in the surrounding text.
Legal Python is generous, but readers and tools pay a tax for wild rebinding.
A teammate who sees count holding text will assume the same everywhere else.
Checker tools such as mypy can only help if names keep a stable type, and the
community guidance in PEP 8 asks exactly for that stability. Keep each
name described by one word.
Python lets a name switch types freely, and beginners should not copy that habit from the language. Pick a name whose wording describes one kind of value, and keep it on that kind for the whole program. Future you will read it, and checkers will thank current you.
Rules for legal names, and the PEP 8 style
Python enforces a small set of hard rules for names, and the community adds a second layer of taste on top.
- A name may contain letters, digits and underscores.
- A name must not start with a digit.
- Names are case-sensitive, so
Teaandteaare different names. - Reserved keywords such as
class,if,for,whileandlambdamust not be used as names.
# legal and readable
tea_price = 0.10
MAX_TABLES = 12
# legal, but foreign to Python eyes
teaPrice = 0.10
# syntax errors
2nd_price = 9.99 # cannot start with a digit
my-price = 9.99 # a hyphen is read as subtraction, so this is not a name
class = "cs101" # class is a reserved keywordPEP 8, the community style guide, then converts taste into convention:
- snake_case for ordinary names:
tea_price,order_total. - UPPER_SNAKE_CASE for values treated as constants:
MAX_TABLES. - No single letters
l,OorI, which read as digit shapes on screens. - Descriptive over clever:
croissant_pricebeatscp.
How to answer
Multiple-choice question with 4 options. Select one option, then choose Check Answer.
A script runs `x = 5` on one line and later runs `x = "five"` on another line. Which statement is accurate about what Python does and what good practice suggests?
How to answer
Multiple-choice question with 4 options. Select one option, then choose Check Answer.
Which of these names is both legal in Python and in the shape that PEP 8 recommends for a customer postcode value?
What a rebinding looks like inside memory
One more view of the sticky note model, this time for three famous lines. The snippet below is worth stepping through slowly, because it separates names from objects more sharply than any definition could.
x = 3 # one int object is built, x is bound to it
y = x + 1 # x is read, 4 is computed, a NEW int object is built, y binds to it
x = "three" # a str object is built, and x is rebound away from the intThe sequence diagram shows the same three lines as a conversation between the source code and the object store. Watch that every assignment rebinds one name, while the objects themselves keep existing.
Description
A sequence diagram for the section What a rebinding looks like inside memory, showing the order of messages and replies described in the surrounding text.
After the third line, x holds text and y still holds 4. The int object 3
loses its last public name, and Python cleans it up in the background without
any help from you. Assignment is a fire-and-forget note change, never a
deletion of data.
2. The Value Quiver: Literals You Will Meet First
A literal is a value spelled directly into source code. The five beginners below cover the overwhelming majority of programs you will read this year.
Literal | Type | Everyday meaning |
|---|---|---|
42 | int | a count, a quantity, an index |
3.14 | float | a measurement with a fractional part |
"hello" | str | text, also called a string |
True | bool | a yes or no answer |
None | NoneType | an explicit empty slot |
count = 42 # int
ratio = 3.14 # float
greeting = "hello" # str
flag = True # bool
empty = None # NoneType, the deliberate nothingThe mindmap gathers the full quiver in one picture, including the two corners
that matter later: exact money arithmetic with decimal.Decimal, and the
frozendict built-in that Python 3.15 added for fixed, immutable mappings.
Description
A mind map for the section 2. The Value Quiver: Literals You Will Meet First, grouping the ideas described in the surrounding text.
Use the bucket exercise to lock the classification in. One item is a trap that catches almost every beginner the first time, so read before you drag.
How to answer
Sorting exercise. Place each item into the correct bucket, then check your answer.
Sort each literal into its type bucket
Drag every card into the bucket that describes the object it creates in Python.
Items, click an item, then click a bucket to place it:
int
Empty, click to place the selected item
float
Empty, click to place the selected item
text
Empty, click to place the selected item
boolean
Empty, click to place the selected item
absence
Empty, click to place the selected item
3. Whole Numbers, Real Numbers and Booleans
int: digits without a ceiling
Python ints grow until memory says stop. There is no value at which + 1
suddenly wraps around or errors out, which removes a whole mistake category
you would otherwise inherit from languages with fixed-width integers.
>>> digits = len(str(2 ** 1000)) # how wide is 2 to the power of 1000
>>> digits
302📚 Did you know? The official library documentation states that Python
int values are limited only by memory. A beginner laptop computes
2 ** 1000, all 302 decimal digits, instantly, while a fixed 64-bit integer
overflows just past 9223372036854775807.
float: fast, approximate, 64 bits
Floats cover measurements and anything with a decimal point. CPython stores each one as an IEEE 754 binary64 double: a 53 bit mantissa plus an exponent, all in base 2. Numbers that need no binary tail, such as 0.5, 3 and 271.75, sit exactly in a float. Numbers such as 0.1 do not, and the tail is the seed of the receipt joke.
The float closest to 0.1 is slightly bigger than one tenth, and that tiny excess survives every calculation. The float limits themselves are visible constants:
>>> import sys
>>> sys.float_info.max
1.7976931348623157e+308
>>> sys.float_info.max * 2
inf
>>> sys.float_info.min
2.2250738585072014e-308bool: two values that are secretly numbers
True and False are the only bool literals, and comparisons produce them.
A detail surprises everyone eventually: bool is a subclass of int, so True
works as 1 and False works as 0.
>>> 5 > 3
True
>>> 3 == 4
False
>>> True == 1
True
>>> type(True)
<class 'bool'>📚 Did you know? bool is a subclass of int, and True == 1 evaluates to
True. Language history explains it: the bool type was added to Python 2.3
on top of the existing integer world, and the arithmetic compatibility was
kept deliberately.
A conversion walkthrough you can type as you read
The built-in functions type(), int(), float() and str() do the day to
day work of checking and moving between kinds. Each line below is real, so
type them into a REPL and confirm them.
>>> type(42) # the int family classifier
<class 'int'>
>>> type(3.14)
<class 'float'>
>>> type("42") # quotes change everything
<class 'str'>
>>> int("42") # text to a whole number
42
>>> float("3.14") # text to a floating point number
3.14
>>> str(3.14) # a number to its text form
'3.14'
>>> int(3.99) # a float to an int TRUNCATES
3
>>> int("hello")
Traceback (most recent call last):
...
ValueError: invalid literal for int() with base 10: 'hello'Three behaviors matter most in daily work:
int()applied to a float truncates toward zero, and never rounds.int()applied to text that is not a number raisesValueErrorinstead of guessing.- Converting user input matters constantly, because
input()hands you a str and arithmetic needs an int or float.
int(7.9) is 7, not 8, by design. When you want school rounding, call
round(7.9), which returns 8. When you want a safe money conversion, reach
for round on purpose and say so in a comment.
How to answer
Multiple-choice question with 4 options. Select one option, then choose Check Answer.
You type `type(True)` in a Python REPL. Which output is printed?
How to answer
Multiple-choice question with 4 options. Select one option, then choose Check Answer.
A script contains the line `discount = int(7.9)`. Which value is stored by that conversion, and why?
4. The 0.1 plus 0.2 Problem, and the Two Honest Cures
Return to the receipt. The float nearest 0.1 carries a small positive excess, and the float nearest 0.2 carries another. The sum of the float excesses represents the value that Python is honest about:
>>> 0.1 + 0.2
0.30000000000000004
>>> 0.1 + 0.2 == 0.3
FalseNothing rounded wrong. Binary floating point has no exact code for one tenth, so the answer is naturally the nearest representable value. Two cures cover everyday needs, and choosing between them is a real engineering decision.
Description
A flowchart for the section 4. The 0.1 plus 0.2 Problem, and the Two Honest Cures, showing the path and decision points described in the surrounding text.
Cure one: decimal.Decimal for money. The Decimal type works in base ten,
so one tenth, two tenths and their sum behave exactly as the decimal payroll
in your head expects it. Two details matter: build Decimal values from quoted
text, never from a float, and keep floats completely out of the arithmetic.
>>> from decimal import Decimal
>>> Decimal("0.1") + Decimal("0.2")
Decimal('0.3')
>>> Decimal("0.1") + Decimal("0.2") == Decimal("0.3")
True
>>> Decimal(0.1) + Decimal(0.2) # the wrong construction, from floats
Decimal('0.3000000000000000166533453693773481063544750213623046875')Cure two: math.isclose for comparisons. When floats are the right tool
but you must compare, compare with a tolerance instead of ==.
>>> import math
>>> math.isclose(0.1 + 0.2, 0.3)
TrueFloat arithmetic is approximate by design and money deserves exactness.
Cent counters stored as int, or decimal.Decimal built from quoted text,
are the two robust choices. A float total can disagree with the ledger by a
fraction of a cent, repeated across a million orders, which is a genuine
business problem rather than a printing quirk.
How to answer
Multiple-choice question with 2 options. Select one option, then choose Check Answer.
You claim in a code review that `0.1 + 0.2 == 0.3` evaluates to `True` in Python. Is the claim true or false?
How to answer
Multiple-choice question with 4 options. Select one option, then choose Check Answer.
A payroll script must store wage and salary values and produce totals that match decimal arithmetic exactly. Which value kind should the script use for the money math, once the values are in?
How to answer
Matching exercise with 5 pairs. Match each item on the left with its answer on the right.
Match each type to the literal that represents it.
Drag answers from here:
5. Text: the Immutable String
Strings hold text. Single and double quotes are equal, so 'hi' and "hi"
build the same object. The backslash starts escape sequences for characters
that are inconvenient to type directly, which gives you newlines, quotes and
raw text modes.
>>> sentence = "she said \"hi\"\nthen left"
>>> print(sentence)
she said "hi"
then left
>>> pattern = r"\d+" # a raw string keeps backslashes literal
>>> pattern
'\\d+'
>>> poem = """Roses tint red,
... violets stay blue,
... Python keeps the quotes
... open for you.""" # triple quotes span real linesStrings are immutable. No method edits a string in place; each one builds and returns a fresh string. This single fact explains every surprising result a beginner meets when calling string methods.
>>> name = "anna"
>>> name.upper()
'ANNA'
>>> name
'anna'
>>> shout = name.upper() # shout binds to the new string📚 Did you know? word.upper() does not change word even when you
call it twice. Because str is immutable, the only way to keep the shout is
to bind it, as in shout = name.upper(). Dictionaries, not strings, are
where in-place edits happen, and that lesson arrives later in the course.
The everyday toolkit covers most text chores:
>>> " iced tea ".strip()
'iced tea'
>>> "iced tea".split()
['iced', 'tea']
>>> "iced tea".replace("tea", "coffee")
'iced coffee'
>>> len("iced tea")
8Slicing, and the end index is exclusive
Text is ordered, indexes count from zero, and slices take a start and an end.
Keep one rule in memory and slicing stops being scary: the end index is
always excluded. Slicing [0:4] therefore yields the first four characters,
at positions 0, 1, 2 and 3.
>>> word = "checkout" # c h e c k o u t, indexes 0 to 7
>>> word[0]
'c'
>>> word[0:4]
'chec'
>>> word[4:8]
'out'
>>> word[0:4] + word[4:8]
'checkout'Positions 0 through 3 produce chec, then positions 4 through 7 produce
out. The halves join back into the full word, which is a quick self-check
that your end indexes are right.
6. f-strings: Text That Interpolates
An f-string is a string prefixed with an f that lets braces interpolate
values straight into text. It is the standard tool for readable output, and
format specs after the colon, such as :.2f, control the rendering.
>>> total = 0.1 + 0.2
>>> print(f"Total: {total:.2f}")
Total: 0.30The :.2f spec rounds the display to two decimal places, while the stored
float itself stays as float as ever. Text interpolation without formatting
works everywhere you would otherwise join strings:
>>> item = "tea"
>>> price = 1.25
>>> line = f"{item}: {price}"
>>> line
'tea: 1.25'Fill the blanks below to rebuild the total line and the coins counter, then check yourself. The first blank makes the braces interpolate.
How to answer
Fill-in-the-blank exercise with 2 blanks. Type the missing words, then check your answer.
Complete the summary line and the conversion. The first blank turns the text into an f-string and the second fills the coins conversion.
blank"Total: {total}" coins = blank(cash_text)
Word Bank, drag words into the blanks above:
Older tutorials show two older styles, .format() and the % operator.
Read them when you inherit code, but write f-strings for new work; the
official tutorial recommends them for interpolation, and 3.14 even adds a
companion syntax called a t-string for safe templates, covered in the
updates box below.
7. None and Reading an Absence
None is the type NoneType and stands for an explicit empty slot. It says the
value is deliberately missing, rather than zero, empty text or a blank.
>>> found = None
>>> type(found)
<class 'NoneType'>
>>> found is None
TrueFunctions that reach their end without a return statement hand back None, so a variable holding the result of such a function looks empty for a reason:
>>> result = None
>>> result is None
True
>>> result == None
TrueBoth comparisons above produce True, but only one is considered good style. PEP 8 and the official documentation recommend the identity form for None, because identity is exactly the question you mean: is this the one None object, or any other value?
Write result is None, not result == None, when checking for absence.
Some objects override equals comparison in ways that turn == None into
the wrong answer, while identity comparison cannot be fooled.
8. Annotations: Documentation for Readers and Checkers
Comments explain the why; annotations describe the what. A type annotation next to a parameter or a function result records what kind of value is meant, and tools such as mypy and the editors use them to check your work.
def total(price: float, quantity: int) -> float:
"""Return the price times the quantity."""
return price * quantityThe annotations above are documentation. Python records them, prints them when asked, and lets checker tools read them, yet the function itself still accepts whatever a caller passes. This is what the official tutorial calls optional static checking: enforcement comes from the tools, not from the runtime.
Annotations do not enforce anything at run time by themselves. A function annotated as returning float still runs and returns text if its body says so, until a checker like mypy or a teacher's test suite says otherwise. Treat annotations as promises that tools verify, not as a fence.
9. The Type World as of November 2026
2026 Updates (as of November 2026)
What changed recently, and why older tutorials look different. Every line below was checked against a primary source in November 2026:
- Python 3.15.2 is the current stable release (3.15.0 shipped on 2026-10-09, and 3.15.2 followed on 2026-10-25). Tutorials that still call earlier series the latest are drifting behind.
frozendictis a new built-in in 3.15 (PEP 814, shipped 2026-10-09). It holds an immutable, hashable mapping, which suits fixed configuration data; a plain dict stays the everyday choice.- UTF-8 became the default text encoding in 3.15 (PEP 686). Opening a text file without naming an encoding now reads and writes UTF-8 across platforms, which removes a classic source of mojibake.
- Template strings
t"..."arrived in 3.14 (PEP 750, released 2025-10-07). A t-string behaves like an f-string and records template parts instead of only producing text, so frameworks can render values safely. - Python 3.10 reached end of life on 2026-10-01. Machines still on 3.10 receive no fixes, so a beginner starting a project should use a supported series such as 3.15.
📚 Did you know? frozendict in Python 3.15 is a built-in type, which
is unusual company: int, str and dict took more than thirty years to get a
sibling. PEP 814 describes it as an immutable, hashable mapping, so it can
even serve as a dictionary key where a regular dict cannot.
Practice Questions
Answer these before the next lesson. Each stem stands on its own, so review sessions can show them without any surrounding page. Inline questions earlier in the sections count too, and ids stay stable for review scheduling.
How to answer
Multiple-choice question with 4 options. Select one option, then choose Check Answer.
In a REPL, `word = "checkout"` is set and then `print(word[0:4])` runs. What is printed?
How to answer
Multiple-choice question with 4 options. Select one option, then choose Check Answer.
A program sets `name = "leaf"` and runs `name.upper()`. What does `name` contain after that line, and what does the call itself produce?
How to answer
Multiple-choice question with 4 options. Select one option, then choose Check Answer.
A reviewer sees `if result == None:` inside a pull request and prefers another form. Which check matches PEP 8 guidance for testing against None?
How to answer
Multiple-choice question with 2 options. Select one option, then choose Check Answer.
A function is declared `def total(price: float) -> float:` and its body runs `return "3"`. Does the annotation stop that wrong return at run time in plain Python?
How to answer
Multiple-choice question with 4 options. Select one option, then choose Check Answer.
Which listing shows only legal Python variable names?
What Comes Next
You can now read the receipt joke in full: tea_price and croissant_price
are names bound to float objects, and float objects speak binary. The next
lessons turn these single values into structures, ordering them into lists and
tuples, and then into mappings. Before moving on, the video below works as a
parallel full-course reference, and its data types chapters restate this
lesson's material from a second voice.
Description
A video walkthrough that supports the section What Comes Next and repeats its main ideas in a second voice.
Dig deeper
- The official tutorial on numbers, strings and variablesPython 3.15 documentation
- Built-in types reference for int, float, str, bool and NonePython 3.15 documentation
- PEP 8, the style guide for Python codePEP index
- What is new in Python 3.15, including frozendict and UTF-8 defaultsPython 3.15 documentation
- uv, the fast package and interpreter manager to install Python 3.15Astral
Key Takeaways:
- A Python variable is a name bound to an object. Assignment rebinds a name, rebinding is legal across kinds, and the type belongs to the object rather than the name.
- The five first literals are
42as int,3.14as float,"hello"as str,Trueas bool andNoneas the deliberate empty slot, each with a type you can confirm withtype(). - int grows without a fixed ceiling, while float is an IEEE 754 binary64
double that cannot hold 0.1 exactly, which is why
0.1 + 0.2prints 0.30000000000000004. - Money math should use
decimal.Decimalbuilt from quoted text, and float comparisons should usemath.iscloserather than==. int(3.99)truncates toward zero and never rounds; useround()when school rounding is the actual requirement.- Strings are immutable, so methods such as
.upper()return new strings, and slices treat the end index as exclusive, so"checkout"[0:4]ischec. - f-strings interpolate values with braces, and format specs such as
:.2fchange only the display, never the stored float. - Compare against None with
is None, the identity form that PEP 8 asks for. - Annotations such as
price: floatdocument and feed checker tools; the runtime itself does not enforce them. - As of November 2026, Python 3.15.2 is current,
frozendictis a new built-in, UTF-8 is the default encoding, and Python 3.10 is end of life, so a fresh project starts on a supported series.
References
Every factual claim links to its source, with the access date printed so the source can be rechecked.