Ch 2.4 Type Casting
Type casting is the process of converting data from one type to another. Python supports both implicit and explicit type casting.
1. Implicit Type Casting
Python automatically converts types. This happens most often in numeric operations.
# int + float → float (automatic conversion)
result = 5 + 2.0
print(result) # 7.0
print(type(result)) # <class 'float'>
# int + bool → int (bool is a subclass of int)
result2 = 10 + True
print(result2) # 11
print(type(result2)) # <class 'int'>
result3 = 10 + False
print(result3) # 10
# int + complex → complex
result4 = 3 + (2 + 1j)
print(result4) # (5+1j)
print(type(result4)) # <class 'complex'>
# Implicit conversion priority: complex > float > int
Python does not perform implicit conversions that could cause data loss. For example,
float + strraises a TypeError.
# This raises an error — no implicit conversion
# result = 5 + "10" # TypeError: unsupported operand type(s) for +: 'int' and 'str'
2. Explicit Type Casting
The developer manually converts types.
int() Conversion
# float → int (truncation, NOT rounding!)
print(int(3.9)) # 3 (truncated!)
print(int(-3.9)) # -3 (truncated toward zero)
print(int(3.1)) # 3
# str → int
print(int("42")) # 42
print(int(" 100 ")) # 100 (leading/trailing whitespace removed automatically)
# bool → int
print(int(True)) # 1
print(int(False)) # 0
# Base conversion (from string)
print(int("1010", 2)) # 10 (binary "1010" → integer 10)
print(int("ff", 16)) # 255 (hexadecimal "ff" → integer 255)
print(int("17", 8)) # 15 (octal "17" → integer 15)
float() Conversion
# int → float
print(float(42)) # 42.0
# str → float
print(float("3.14")) # 3.14
print(float("1e3")) # 1000.0
print(float("inf")) # inf
print(float("-inf")) # -inf
print(float("nan")) # nan
# bool → float
print(float(True)) # 1.0
str() Conversion
# Any type → string
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({"a": 1})) # "{'a': 1}"
bool() Conversion
# Various types → bool
print(bool(1)) # True
print(bool(0)) # False
print(bool(-1)) # True (any non-zero value is True)
print(bool("hello")) # True
print(bool("")) # False (empty string)
print(bool([1, 2])) # True
print(bool([])) # False (empty list)
print(bool(None)) # False
print(bool({})) # False (empty dictionary)
print(bool({0})) # True (set containing 0 — not empty)
list(), tuple(), set() Conversions
# Converting between collection types
my_list = [1, 2, 3, 3, 2]
my_tuple = tuple(my_list) # (1, 2, 3, 3, 2)
my_set = set(my_list) # {1, 2, 3} (duplicates removed)
print(list(my_tuple)) # [1, 2, 3, 3, 2]
print(list(my_set)) # [1, 2, 3] (order not guaranteed)
# string → list (each character becomes an element)
chars = list("Python")
print(chars) # ['P', 'y', 't', 'h', 'o', 'n']
# range → list
numbers = list(range(1, 11))
print(numbers) # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# dict → list (list of keys)
d = {"a": 1, "b": 2, "c": 3}
print(list(d)) # ['a', 'b', 'c'] (keys)
print(list(d.values())) # [1, 2, 3] (values)
print(list(d.items())) # [('a', 1), ('b', 2), ('c', 3)]
3. Handling Failed String-to-Number Conversion
# Failed conversion raises ValueError
# int("hello") # ValueError: invalid literal for int() with base 10: 'hello'
# float("abc") # ValueError: could not convert string to float: 'abc'
# Safe conversion with try/except
def safe_int(value: str, default: int = 0) -> int:
"""Safely converts a string to an integer."""
try:
return int(value)
except (ValueError, TypeError):
return default
print(safe_int("42")) # 42
print(safe_int("hello")) # 0 (default)
print(safe_int("3.14")) # 0 (fails due to decimal point)
print(safe_int(None)) # 0
def safe_float(value, default=0.0):
try:
return float(value)
except (ValueError, TypeError):
return default
print(safe_float("3.14")) # 3.14
print(safe_float("hello")) # 0.0
# Real-world example: handling user input
def get_user_age() -> int:
while True:
user_input = input("Enter your age: ")
try:
age = int(user_input)
if age < 0 or age > 150:
print("Please enter a valid age (0-150).")
continue
return age
except ValueError:
print(f"'{user_input}' is not a valid number. Please try again.")
4. Falsy Values
These are the values that evaluate to False when converted with bool().
falsy_values = [
None, # NoneType
False, # bool
0, # int
0.0, # float
0j, # complex
"", # empty string
b"", # empty bytes
[], # empty list
(), # empty tuple
{}, # empty dictionary
set(), # empty set
]
for val in falsy_values:
print(f"bool({val!r:15}) = {bool(val)}")
# All print False
# Practical pattern using falsy values
def process_name(name: str | None) -> str:
if not name: # handles None, "", and " ".strip() result ""
return "Anonymous"
return name.strip().title()
print(process_name(None)) # Anonymous
print(process_name("")) # Anonymous
print(process_name("alice")) # Alice
5. Base Conversion
number = 255
# Integer → base string
print(bin(number)) # '0b11111111'
print(oct(number)) # '0o377'
print(hex(number)) # '0xff'
# Output without prefix
print(format(number, 'b')) # '11111111'
print(format(number, 'o')) # '377'
print(format(number, 'x')) # 'ff'
print(format(number, 'X')) # 'FF' (uppercase)
# Specify width
print(format(number, '08b')) # '11111111' (8 digits)
print(format(number, '#010b')) # '0b11111111' (10 digits including prefix)
# Base string → integer
print(int("11111111", 2)) # 255 (binary)
print(int("377", 8)) # 255 (octal)
print(int("ff", 16)) # 255 (hexadecimal)
print(int("FF", 16)) # 255 (case-insensitive)
print(int("0xff", 16)) # 255 (prefix included)
# Real-world example: RGB color conversion
def hex_to_rgb(hex_color: str) -> tuple[int, int, int]:
"""Converts #RRGGBB format to an RGB tuple."""
hex_color = hex_color.lstrip('#')
r = int(hex_color[0:2], 16)
g = int(hex_color[2:4], 16)
b = int(hex_color[4:6], 16)
return r, g, b
def rgb_to_hex(r: int, g: int, b: int) -> str:
"""Converts RGB values to #RRGGBB format."""
return f"#{r:02X}{g:02X}{b:02X}"
print(hex_to_rgb("#FF5733")) # (255, 87, 51)
print(rgb_to_hex(255, 87, 51)) # #FF5733
Pro Tips: Custom Type Casting with
__int__, __float__, and __bool__ Magic MethodsPython's int(), float(), and bool() functions internally call an object's magic methods. By implementing these, custom classes can support type casting.
class Temperature:
"""A class representing a temperature in Celsius"""
def __init__(self, celsius: float):
self.celsius = celsius
def __int__(self) -> int:
"""Called by int() — returns Celsius rounded to the nearest integer"""
return round(self.celsius)
def __float__(self) -> float:
"""Called by float() — returns the Celsius value"""
return float(self.celsius)
def __bool__(self) -> bool:
"""Returns True if above absolute zero (0K = -273.15°C)"""
return self.celsius > -273.15
def __str__(self) -> str:
return f"{self.celsius}°C"
def to_fahrenheit(self) -> float:
return self.celsius * 9 / 5 + 32
temp = Temperature(36.6)
print(int(temp)) # 37
print(float(temp)) # 36.6
print(bool(temp)) # True
freezing = Temperature(-273.15)
print(bool(freezing)) # False (absolute zero)
print(str(temp)) # 36.6°C
print(temp.to_fahrenheit()) # 97.88
# Practical example: a Money class
class Money:
def __init__(self, amount: float, currency: str = "USD"):
self.amount = amount
self.currency = currency
def __int__(self) -> int:
return int(self.amount)
def __float__(self) -> float:
return float(self.amount)
def __bool__(self) -> bool:
return self.amount > 0
def __str__(self) -> str:
return f"{self.amount:,.2f} {self.currency}"
price = Money(29.90)
print(str(price)) # 29.90 USD
print(bool(price)) # True
print(bool(Money(0))) # False
You now understand the various ways to perform type casting. The next chapter covers type hints, which have become increasingly important in Python 3.12+.