Python Type Casting — Language Specification
1. Spec Reference
- Primary source: Python Language Reference, §3.2 — The standard type hierarchy https://docs.python.org/3/reference/datamodel.html#the-standard-type-hierarchy
- Built-in functions (
int, float, str, etc.): https://docs.python.org/3/library/functions.html - Numeric tower (PEP 3141): https://docs.python.org/3/library/numbers.html
__int__, __float__, __index__, __trunc__ protocols: https://docs.python.org/3/reference/datamodel.html#emulating-numeric-types - Python 3.12 standard: All rules here apply to CPython 3.12 unless otherwise noted.
Python has no special cast syntax; type conversion is performed through constructor calls and built-in functions. The grammar for a call expression covers all type conversions:
call ::= primary "(" [argument_list [","] | comprehension] ")"
argument_list ::= positional_arguments ["," starred_and_keywords]
["," keywords_arguments]
| starred_and_keywords ["," keywords_arguments]
| keywords_arguments
positional_args ::= assignment_expression ("," assignment_expression)*
2.1 Built-in Conversion Functions
int_call ::= "int" "(" [x [, base]] ")"
float_call ::= "float" "(" [x] ")"
complex_call::= "complex" "(" [real [, imag]] ")"
str_call ::= "str" "(" [object [, encoding [, errors]]] ")"
bytes_call ::= "bytes" "(" [source [, encoding [, errors]]] ")"
bool_call ::= "bool" "(" [x] ")"
list_call ::= "list" "(" [iterable] ")"
tuple_call ::= "tuple" "(" [iterable] ")"
set_call ::= "set" "(" [iterable] ")"
frozenset_call ::= "frozenset" "(" [iterable] ")"
dict_call ::= "dict" "(" [mapping_or_iterable] ")" | "dict" "(" **kwargs ")"
3. Core Rules and Constraints
3.1 Explicit vs Implicit Conversion
- Python is strongly typed: it does NOT perform silent coercion between unrelated types.
"5" + 3 raises TypeError — unlike JavaScript or Perl. - Implicit numeric widening is the exception:
int + float → float; float + complex → complex. This follows the numeric tower (PEP 3141): bool <: int <: float <: complex. - All other conversions must be explicit.
3.2 int() Conversion Rules
int(x) where x is a number: truncates toward zero (same as math.trunc()). int(x, base) where x is str/bytes/bytearray: parses the string in the given base. - Valid bases: 0 (auto-detect from prefix) or 2–36.
- Base 0 interprets
0b, 0o, 0x prefixes; no prefix means decimal. int does not accept float-formatted strings like "3.14" — raises ValueError. - Calls
x.__int__() if defined; falls back to x.__index__(), then x.__trunc__(). - Since Python 3.11, converting a very large string to int can be limited by
sys.set_int_max_str_digits().
3.3 float() Conversion Rules
float(x) accepts numeric types or string representations. - Strings:
"3.14", "1e5", "inf", "-Inf", "nan" (case-insensitive). float does not accept non-numeric strings — raises ValueError. - Calls
x.__float__() if defined; falls back to x.__index__().
3.4 str() Conversion Rules
str(obj) calls obj.__str__(). - If
__str__ is not defined (or returns NotImplemented), falls back to obj.__repr__(). str(bytes_obj) without encoding gives "b'...'" — not decoded. str(bytes_obj, encoding) calls bytes_obj.decode(encoding).
3.5 bytes() Conversion Rules
bytes(int): creates a zero-filled bytes object of that length. bytes(iterable): each element must be in [0, 255]. bytes(str, encoding): encodes string to bytes. bytes(bytes_like): copies the bytes-like object.
3.6 bool() Conversion Rules
- Calls
x.__bool__(); if not defined, calls x.__len__(). - Returns
True or False. - Zero, empty sequences/mappings/sets, and
None are falsy; everything else is truthy.
3.7 Container Conversion
list(iter), tuple(iter), set(iter), frozenset(iter): consume the iterable. - Duplicate handling:
set/frozenset deduplicate using __hash__ and __eq__. - Order:
list and tuple preserve iteration order; set is unordered.
4. Type Rules (Dunder Methods and Protocols)
4.1 Numeric Conversion Protocol
object.__int__(self) -> int # called by int()
object.__float__(self) -> float # called by float()
object.__complex__(self) -> complex # called by complex()
object.__index__(self) -> int
# __index__ must return an exact int (not a subclass).
# Used by: int(), bin(), oct(), hex(), operator.index(), slicing.
# If defined, the object can be used wherever an integer index is needed.
object.__trunc__(self) -> int # called by math.trunc(); fallback for int()
object.__floor__(self) -> int # called by math.floor()
object.__ceil__(self) -> int # called by math.ceil()
4.2 String Conversion Protocol
object.__str__(self) -> str # called by str(), print(), f-strings (with __format__)
object.__repr__(self) -> str # called by repr(); used as fallback by str()
object.__format__(self, format_spec: str) -> str
# Called by format() and f-string format specifications.
# If format_spec is empty, behaves like str(self).
object.__bytes__(self) -> bytes # called by bytes()
4.3 __index__ vs __int__
__index__ guarantees lossless integer conversion (the object represents an integer exactly). __int__ may truncate or round (e.g., float.__int__ truncates). - Slicing,
bin(), oct(), hex() require __index__, not __int__.
bool → int → float → complex
int + float → float (int is promoted)
float + complex → complex (float is promoted)
int + complex → complex (int is promoted)
These follow the numeric coercion rules in the numbers abstract base class hierarchy.
5. Behavioral Specification
5.1 int() Behavior Table
| Input | Result | Notes |
int(3) | 3 | identity-like |
int(3.7) | 3 | truncates toward zero |
int(-3.7) | -3 | toward zero |
int(True) | 1 | bool is int |
int("42") | 42 | decimal string |
int("0b1010", 0) | 10 | auto-detect base |
int("ff", 16) | 255 | hex |
int("3.14") | ValueError | |
int("") | ValueError | |
int(None) | TypeError | |
5.2 float() Behavior Table
| Input | Result | Notes |
float(3) | 3.0 | int → float |
float("3.14") | 3.14 | |
float("inf") | inf | case-insensitive |
float("nan") | nan | |
float(True) | 1.0 | |
float("abc") | ValueError | |
float(None) | TypeError | |
5.3 str() Behavior Table
| Input | Result | Notes |
str(42) | "42" | |
str(3.14) | "3.14" | |
str(True) | "True" | |
str(None) | "None" | |
str([1,2]) | "[1, 2]" | calls repr on elements |
str(b"hi") | "b'hi'" | NOT decoded |
str(b"hi", "utf-8") | "hi" | decoded |
5.4 repr() vs str()
repr() should return an unambiguous, ideally Python-parseable representation. str() should return a human-readable representation. - For built-in types,
repr() wraps strings in quotes; str() does not. eval(repr(obj)) == obj is a design goal, not a guarantee.
6. Defined vs Undefined Behavior
6.1 Defined
int(float_val) always truncates toward zero (not floor, not round). bool(x) always returns True or False, never other values. float("nan") != float("nan") is True (IEEE 754). - Constructors (
list, tuple, etc.) fully consume the passed iterator. int(str_val, base=0) respects 0b, 0o, 0x prefixes.
6.2 Undefined / Implementation-Defined
repr() output format: For user-defined classes without __repr__, CPython gives <ClassName object at 0x...>. The hex address is implementation-defined. float string representation: Since Python 3.1, repr(float) gives the shortest round-trip string; the exact output depends on the C library's dtoa implementation. - Large int-to-str conversion time: Converting very large integers (millions of digits) to strings is O(n^2) in CPython (improved in 3.11 with better algorithms). The string conversion limit (
sys.set_int_max_str_digits()) defaults to 4300.
7. Edge Cases from the Spec (CPython-Specific Notes)
7.1 int() Truncates Toward Zero (Not Floor)
print(int(3.9)) # 3 (not 4)
print(int(-3.9)) # -3 (not -4)
# Compare with math.floor:
import math
print(math.floor(-3.9)) # -4
print(math.trunc(-3.9)) # -3 (same as int())
7.2 String to int With Base
print(int("0b1010", 0)) # 10 (base 0 = auto)
print(int("0o17", 0)) # 15
print(int("0xFF", 0)) # 255
print(int("FF", 16)) # 255
print(int("77", 8)) # 63
print(int("11", 2)) # 3
# Underscore separators allowed (Python 3.6+):
print(int("1_000_000")) # 1000000
7.3 bool Subclass of int
print(int(True)) # 1
print(int(False)) # 0
print(True + True) # 2
print(str(True)) # "True" (not "1")
print(repr(True)) # "True"
7.4 bytes() From Integer vs From Iterable
print(bytes(5)) # b'\x00\x00\x00\x00\x00' — 5 zero bytes
print(bytes([65, 66, 67])) # b'ABC'
print(bytes("hello", "utf-8")) # b'hello'
# TRAP: bytes("hello") WITHOUT encoding raises TypeError
try:
bytes("hello")
except TypeError as e:
print(e) # string argument without an encoding
7.5 __index__ for Custom Integers
class Meter:
def __init__(self, value: int):
self._value = value
def __index__(self) -> int:
return self._value
m = Meter(3)
data = [10, 20, 30, 40, 50]
print(data[m]) # 40 — __index__ used for subscript
print(bin(m)) # 0b11 — __index__ used by bin()
print(hex(m)) # 0x3
7.6 Large Integer String Conversion Limit (Python 3.11+)
import sys
print(sys.get_int_max_str_digits()) # 4300 by default
# Override (for trusted data only):
sys.set_int_max_str_digits(10000)
big = 10 ** 5000
s = str(big) # now allowed
print(len(s)) # 5001
pi = 3.14159265358979
print(str(pi)) # 3.14159265358979
print(repr(pi)) # 3.14159265358979
print(format(pi, ".2f")) # 3.14
print(f"{pi:.4e}") # 3.1416e+00
8. Version History (PEPs and Python Versions)
| Feature | PEP | Version |
int/float/str as constructors | — | Python 2.2 |
bool type | PEP 285 | Python 2.3 |
| Unification of int/long | PEP 237 | Python 2.2 |
Removal of long type | PEP 3141 | Python 3.0 |
Numeric tower ABCs (numbers) | PEP 3141 | Python 3.0 |
__index__ protocol | PEP 357 | Python 2.5 |
float shortest repr (round-trip) | — | Python 3.1 |
int underscores in int() parsing | PEP 515 | Python 3.6 |
int string conversion limit | — | Python 3.11 |
int.__or__ for union types | PEP 604 | Python 3.10 |
9. Implementation-Specific Behavior
9.1 CPython int → str Algorithm
- CPython 3.10 and earlier: O(n^2) algorithm for large integers.
- CPython 3.11: improved to O(n * log(n)) using divide-and-conquer.
- Default limit of 4300 digits protects against denial-of-service via large conversions.
9.2 CPython float Representation
float.__repr__ uses David Gay's dtoa C library to produce the shortest round-trip decimal. repr(0.1) is "0.1" in Python 3.1+ (was "0.10000000000000001" in Python 2).
9.3 CPython str() and Unicode
- CPython
str uses PEP 393 flexible string representation. str(bytes_obj) (without encoding) returns the Python literal representation, not decoded content.
9.4 PyPy
- PyPy uses similar conversion semantics but may differ in
id() and exact repr() for floats in edge cases. - Large integer conversion performance may differ from CPython.
10. Spec Compliance Checklist
11. Official Examples (Runnable Python 3.10+)
import math
import sys
# ----------------------------------------------------------------
# 1. int() — various inputs
# ----------------------------------------------------------------
print(int(3.9)) # 3 (truncate toward zero)
print(int(-3.9)) # -3 (truncate toward zero, not floor)
print(int("42")) # 42
print(int("0xFF", 16)) # 255
print(int("0b1010", 0)) # 10
print(int(True)) # 1
# ----------------------------------------------------------------
# 2. float() — various inputs
# ----------------------------------------------------------------
print(float(3)) # 3.0
print(float("3.14")) # 3.14
print(float("1e10")) # 10000000000.0
print(float("inf")) # inf
print(float("-INF")) # -inf (case-insensitive)
print(float("nan")) # nan
# ----------------------------------------------------------------
# 3. str() — various inputs
# ----------------------------------------------------------------
print(str(42)) # "42"
print(str(3.14)) # "3.14"
print(str(True)) # "True"
print(str(None)) # "None"
print(str([1, 2, 3])) # "[1, 2, 3]"
print(str(b"hello")) # "b'hello'" (NOT decoded!)
print(str(b"hello", "utf-8")) # "hello" (decoded)
# ----------------------------------------------------------------
# 4. bool() — truth value testing
# ----------------------------------------------------------------
for val in [0, 0.0, "", [], {}, None, False, "x", [0], 1]:
print(f"bool({repr(val)}) = {bool(val)}")
# ----------------------------------------------------------------
# 5. bytes() — three forms
# ----------------------------------------------------------------
print(bytes(5)) # b'\x00\x00\x00\x00\x00'
print(bytes([72, 101, 108])) # b'Hel'
print(bytes("hello", "utf-8")) # b'hello'
# ----------------------------------------------------------------
# 6. Container conversions
# ----------------------------------------------------------------
t = (1, 2, 3)
lst = list(t) # tuple → list
tpl = tuple(lst) # list → tuple
s = set(lst) # list → set (unique, unordered)
fs = frozenset([1,2,3,2,1]) # deduplicated, immutable
print(lst) # [1, 2, 3]
print(s) # {1, 2, 3}
print(fs) # frozenset({1, 2, 3})
# ----------------------------------------------------------------
# 7. dict() from pairs or keyword args
# ----------------------------------------------------------------
d1 = dict([("a", 1), ("b", 2)])
d2 = dict(a=1, b=2)
d3 = dict({"a": 1}, b=2)
print(d1 == d2 == d3) # True
# ----------------------------------------------------------------
# 8. Implicit numeric promotion
# ----------------------------------------------------------------
result = 5 + 2.0 # int + float → float
print(type(result)) # <class 'float'>
result2 = 2.0 + (1+0j) # float + complex → complex
print(type(result2)) # <class 'complex'>
# ----------------------------------------------------------------
# 9. Custom __index__ for slice/index usage
# ----------------------------------------------------------------
class MyInt:
def __init__(self, n):
self._n = n
def __index__(self):
return self._n
m = MyInt(2)
data = [10, 20, 30, 40]
print(data[m]) # 30
print(data[m:]) # [30, 40]
print(bin(m)) # 0b10
# ----------------------------------------------------------------
# 10. __int__ vs __trunc__ vs math functions
# ----------------------------------------------------------------
class Approx:
def __init__(self, v):
self.v = v
def __int__(self):
return int(self.v)
def __float__(self):
return float(self.v)
def __trunc__(self):
return math.trunc(self.v)
def __floor__(self):
return math.floor(self.v)
def __ceil__(self):
return math.ceil(self.v)
a = Approx(3.7)
print(int(a)) # 3
print(float(a)) # 3.7
print(math.trunc(a)) # 3
print(math.floor(a)) # 3
print(math.ceil(a)) # 4
# ----------------------------------------------------------------
# 11. format() with format spec
# ----------------------------------------------------------------
n = 255
print(format(n, "d")) # 255 (decimal)
print(format(n, "x")) # ff (hex lowercase)
print(format(n, "X")) # FF (hex uppercase)
print(format(n, "o")) # 377 (octal)
print(format(n, "b")) # 11111111 (binary)
print(format(3.14159, ".3f")) # 3.142
# ----------------------------------------------------------------
# 12. Large int string conversion limit (Python 3.11+)
# ----------------------------------------------------------------
print(f"Default limit: {sys.get_int_max_str_digits()}") # 4300
# Convert within limit:
n = 10 ** 100
print(str(n)[:20], "...") # first 20 chars
| Section | Topic | URL |
| §3.2 | Standard type hierarchy | https://docs.python.org/3/reference/datamodel.html#the-standard-type-hierarchy |
| §3.3.8 | Emulating numeric types | https://docs.python.org/3/reference/datamodel.html#emulating-numeric-types |
int() | Built-in function | https://docs.python.org/3/library/functions.html#int |
float() | Built-in function | https://docs.python.org/3/library/functions.html#float |
str() | Built-in function | https://docs.python.org/3/library/functions.html#str |
bool() | Built-in function | https://docs.python.org/3/library/functions.html#bool |
bytes() | Built-in function | https://docs.python.org/3/library/functions.html#bytes |
numbers | Numeric ABCs | https://docs.python.org/3/library/numbers.html |
math.trunc | Truncate to int | https://docs.python.org/3/library/math.html#math.trunc |
| PEP 3141 | Numeric tower | https://peps.python.org/pep-3141/ |
| PEP 357 | __index__ protocol | https://peps.python.org/pep-0357/ |
| PEP 285 | bool type | https://peps.python.org/pep-0285/ |