Literals, Numbers, and Bytes
Literals, Numbers, and Bytes
A literal is a value written in the source: 12, 1_850, 0xFF, "window", b"OK". The boring default is to write numbers so a human can read them, keep money in cents or Decimal, and treat bytes as raw and str as text.
Mental model
Python reads an integer in decimal unless you mark a base: 0x hex, 0o octal, 0b binary. Underscores in numbers are ignored: 1_850 is 1850. They exist so a price or a mask is skimmable.
int division with // stays an int. / always gives a float. float is binary floating point. 0.1 + 0.2 is not 0.3. For money, store integer cents or use decimal.Decimal with a string constructor.
A str is Unicode text. bytes is a sequence of 0–255. You move between them with encode and decode and an encoding name, almost always "utf-8". Adding a str to bytes raises TypeError.
Text formatting: an f-string (f"table {n}") is the boring default. str.format still works. % formatting is legacy.
Worked examples
Case 1: Readable numbers and bases
Save as readable_numbers.py.
# readable_numbers.py
def main():
cents = 1_850
seats = 10_000
flags = 0xA5
print(cents, seats, flags)
print(hex(flags), bin(flags))
print(12 / 5)
print(12 // 5)
print(12 % 5)
if __name__ == "__main__":
main()Run:
uv run python readable_numbers.pyOutput:
1850 10000 165
0xa5 0b10100101
2.4
2
2
0xA5 is 165. / is float division even when both sides are ints. // is floor division. % is remainder.
Case 2: Float is the wrong type for a check
# float_check.py
from decimal import Decimal
def main():
print(0.1 + 0.2)
print(Decimal("0.1") + Decimal("0.2"))
soup = 850
tea = 400
print("cents", soup + tea)
print("dollars", (soup + tea) / 100)
if __name__ == "__main__":
main()Run:
uv run python float_check.pyOutput:
0.30000000000000004
0.3
cents 1250
dollars 12.5
Add cents as ints. Convert to dollars only when you print. If you need exact decimal fractions, construct Decimal from a string, not from a float (Decimal(0.1) already has the error).
Case 3: str is text, bytes is raw
# label_bytes.py
def main():
label = "café table 12"
raw = label.encode("utf-8")
print(type(label), label)
print(type(raw), raw)
print(list(raw))
back = raw.decode("utf-8")
print(back)
print(label == back)
if __name__ == "__main__":
main()Run:
uv run python label_bytes.pyOutput:
<class 'str'> café table 12
<class 'bytes'> b'caf\xc3\xa9 table 12'
[99, 97, 102, 195, 169, 32, 116, 97, 98, 108, 101, 32, 49, 50]
café table 12
True
é is two bytes in UTF-8 (195, 169). The b'...' display is Python’s way of showing those bytes. Decode with the same encoding you encoded with.
Case 4: f-strings versus format
# format_ticket.py
def main():
table = 12
cents = 1850
print(f"table {table}: {cents} cents")
print("table {}: {} cents".format(table, cents))
print("table {t}: {c} cents".format(t=table, c=cents))
print(f"table {table:>4}: ${cents / 100:.2f}")
if __name__ == "__main__":
main()Run:
uv run python format_ticket.pyOutput:
table 12: 1850 cents
table 12: 1850 cents
table 12: 1850 cents
table 12: $18.50
Prefer the f-string. Use format specs (:>4, :.2f) inside the braces. Keep str.format when the template itself is a string you loaded from a file.
The trap
Mixing str and bytes, or using float as a running total for money, both fail in ways that look like “Python is weird” until you look at the types.
# mix_text.py
def main():
header = b"TICKET"
name = "12"
print(header + name)
if __name__ == "__main__":
main()Run:
uv run python mix_text.pyOutput (the process exits non-zero):
Traceback (most recent call last):
File "mix_text.py", line 9, in <module>
main()
File "mix_text.py", line 5, in main
print(header + name)
TypeError: can't concat str to bytes
The fix is one side: header + name.encode("utf-8") or header.decode("utf-8") + name. Pick text or raw and convert at the edge (socket, file, subprocess).
The boring rule
- Write
1_850, not1850, when the extra marks help a human. - Use
0x/0bwhen the value is a bit pattern. Do not decorate ordinary counts. - Money: integer cents, or
Decimal("18.50"). Never accumulatefloatdollars. strin the program.bytesat the wire. Encode and decode with"utf-8"unless a protocol names another encoding.- f-strings for almost all formatting.
Try this
- In
readable_numbers.py, print0b1010and0o12and confirm both are ten. - Change
float_check.pyso a 10% service charge is applied in cents with integer arithmetic (cents * 10 // 100). - In
label_bytes.py, decode with"latin-1"instead of"utf-8"and print the result. Put"utf-8"back.