Skip to main content
Advertisement

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 + str raises 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 Methods

Python'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+.

Advertisement