Frontend Labs
⋯

3. Numbers, text, and bytes

3.1 Integers have no overflow

>>> 2 ** 64
18446744073709551616
>>> (2 ** 64) * (2 ** 64)
340282366920938463463374607431768211456
>>> 7 // 2, -7 // 2, 7 % 3, -7 % 3      # floor division and modulo round toward -inf
(3, -4, 1, 2)
>>> 0b1010, 0o17, 0xff, 1_000_000        # literals; _ is a digit separator
(10, 15, 255, 1000000)

Python int grows as needed, so there is no integer overflow (and no overflow vulnerability class like C §28). The cost is that every integer is a heap object of 28+ bytes. A list of a million integers uses about 36 MB in Python and 8 MB as a NumPy int64 array. That difference is Chapter 53.

Since 3.11, converting huge strings to int is limited to 4300 digits by default (sys.set_int_max_str_digits), because the conversion is quadratic and was a denial-of-service vector (CVE-2020-10735).

3.2 Floats are IEEE 754 doubles

>>> 0.1 + 0.2
0.30000000000000004
>>> 0.1 + 0.2 == 0.3
False
>>> import math
>>> math.isclose(0.1 + 0.2, 0.3)
True
>>> float("nan") == float("nan")
False
>>> 1e308 * 10
inf

The Maths guide explains why 0.1 has no exact binary representation. The rules: compare floats with math.isclose, never use floats for money, and expect nan to poison every computation it touches (a common cause of a training loss suddenly reading nan).

For money, use decimal.Decimal with string inputs, or integer cents:

from decimal import Decimal

print(Decimal("0.10") + Decimal("0.20"))   # 0.30
print(Decimal(0.1))                        # 0.1000000000000000055511151231257827021181583404541015625

fractions.Fraction gives exact rational arithmetic when you need it.

3.3 str is a sequence of Unicode code points

s = "naïve café 🐍"
print(len(s))                 # 12: code points, not bytes
print(s[0], s[-1])            # n 🐍
print(s.upper())              # NAÏVE CAFÉ 🐍
print(s.encode("utf-8"))      # b'na\xc3\xafve caf\xc3\xa9 \xf0\x9f\x90\x8d'
print(len(s.encode("utf-8"))) # 17 bytes

Strings are immutable. Building a big string with += in a loop can be quadratic; collect parts in a list and "".join(parts) once.

The operations you will use daily:

line = "  GET /index.html HTTP/1.1  "
print(line.strip().split())             # ['GET', '/index.html', 'HTTP/1.1']
print("a,b,,c".split(","))              # ['a', 'b', '', 'c']
print("-".join(["2026", "10", "04"]))   # 2026-10-04
print("report.csv".removesuffix(".csv"))# report
print("Error: disk".startswith(("Error", "Fatal")))  # True
print(f"{3.14159:.2f} {42:>6} {255:#x} {0.257:.1%}")  # 3.14     42 0xff 25.7%
name, n = "load", 3
print(f"{name=} {n * 2=}")              # name='load' n * 2=6  (debug form)

3.4 bytes is a sequence of integers 0–255

Files, sockets, hashes, and encryption work on bytes, not text. Python 3 keeps the two types strictly separate, which is the right design (the Security guide Ch 5 explains why mixing them causes bugs):

data = b"GET / HTTP/1.1\r\n"
print(data[0])                  # 71: indexing bytes gives an int
print(data[:3])                 # b'GET'
print(data.decode("ascii"))     # str
print(bytes([0x48, 0x69]))      # b'Hi'
print(bytes.fromhex("deadbeef"), b"\xde\xad".hex())

buf = bytearray(b"hello")       # the mutable version
buf[0] = ord("H")
print(buf)                      # bytearray(b'Hello')

The rule: decode bytes to str as soon as they enter your program (from a file, socket, or subprocess), work with str inside, and encode back to bytes at the edge. Always name the encoding; it is almost always "utf-8".

# Mixing them is a TypeError, on purpose:
"abc" + b"def"
# TypeError: can only concatenate str (not "bytes") to str

3.5 Booleans and truthiness

bool is a subclass of int (True == 1). In if and while, every object has a truth value. These are false: None, False, 0, 0.0, "", b"", and empty containers ([], {}, set(), ()). Everything else is true.

items: list[str] = []
if not items:
    print("nothing to do")

and and or return one of their operands, not a bool:

port = None
print(port or 8080)          # 8080
print("" or "default")       # default
print(0 or 8080)             # 8080   <- careful: 0 might be a valid value

Use x if x is not None else default when 0 or "" are legitimate values.


Generated from real-life-python-guide.md · 8 Oct 2026 16:36