How Computers Store Data
Concepts covered: Variables
Run price = 10 and Python does 3 things. It builds an integer object holding 10 somewhere in memory, it records that the object's type is int, and it attaches the name price to that object. The name is a label stuck on the outside; the object is the thing that takes up space. Keeping the label and the object apart explains almost everything else in this lesson.
Bits, bytes and objects
Memory is a long row of numbered slots called bytes, and every value Python builds occupies a run of consecutive bytes starting at some address. The object stores more than the raw number: it carries a type tag so Python knows how to read the bytes, a count of how many names point at it, and then the value itself. That bookkeeping is why a Python integer takes more room than the 8 bytes a C program would spend on the same number.
| Value | Type | Bytes it occupies |
|---|---|---|
| 42 | int | 28 |
| 3.14 | float | 24 |
| True | bool | 28 |
| "hi" | str | 51 |
| "" | str | 49 |
Sizes as reported by CPython 3.9 on a 64-bit machine. A 2-character string costs 2 bytes more than an empty one; the other 49 bytes are the object's bookkeeping.
- 8
- 256
- 64
A float always fits in the same fixed number of bits, so there's a largest value it can hold and a limit to how many digits it keeps. An int has no such ceiling. When a whole number outgrows the bytes it started with, Python allocates a bigger object for it, so arithmetic on huge integers stays exact.
exact = 2 ** 100 + 1
rough = 2.0 ** 100 + 1
print(exact)
print(rough)
print(exact - 2 ** 100)
print(rough - 2.0 ** 100)Output:
1267650600228229401496703205377 1.2676506002282294e+30 1 0.0
The integer keeps all 31 digits, including the 1 added at the end. The float prints as 1.2676506002282294e+30, meaning 1.2676506002282294 times 10 to the power 30. Subtracting 2.0 ** 100 back out leaves 0.0, so the added 1 never reached the stored value. A float holds about 16 significant digits and spends them on the leading part of the number, which means the tail is rounded away. In data work, counts of rows and bytes are whole numbers, and so are timestamps, which is why they're kept as int for as long as possible.
A name points at an object
Assignment with a name on the right side copies the arrow, never the object. After saved = price, both names point at the same integer, and nothing has been duplicated. When price is later assigned 20, Python points price at an object holding 20: a pre-built one for a small number like this, a freshly built one for a larger number. Either way only the price arrow swings, and saved keeps pointing where it always did. Step through the run below and watch each name move.
price = 10
saved = price
price = 20
print(price)
print(saved)
label = "ok"
label = label + "!"
print(label)20 10 ok!
The final 3 lines make the same point with text. label + "!" builds a brand new string "ok!", then the name label is moved onto it. The original "ok" object is unchanged, because + builds its result separately and never edits the string it read from. An object that nothing refers to any more is freed, and its bytes are reused.
What does this program print?
a = 5
b = a
a = a + 1
print(b)- 6
- 5
- a
- The program fails on line 3
In stock = stock - 5 the same name sits on both sides. Python evaluates the right side first, using the object stock points at now, and only then moves the name onto the result. Tracing a program means noting where each name points after every line, as in this table.
| Line | points | start |
|---|---|---|
| points = 30 | 30 | not yet assigned |
| start = points | 30 | 30 |
| points = points + 8 | 38 | 30 |
About this lesson
This section is part of the Python Expressions: Beginner lesson on DataDriven, the home of data engineering. Lessons cover SQL, Python, Spark, data modeling and pipeline architecture, and each section pairs its explanation with worked examples and practice you run as you go.