Python Type Casting
Type casting is the process of converting a value from one data type to another — for example, turning the text "42" into the integer 42, or a floating-point number into a string. Python performs some conversions automatically (implicit casting), but most of the time you convert types explicitly using built-in functions like int(), float(), str(), and bool(). Understanding type casting matters because Python is dynamically but strongly typed: it will not silently combine a string and a number the way some languages do, so you must convert values yourself before using them together.
Overview: How Type Casting Works
Every value in Python has a type — int, float, str, bool, list, and so on — and that type determines which operations are valid and how the value behaves. Type casting (also called type conversion) changes a value from one type to another, producing a new object rather than mutating the original. The source value is never modified in place.
Python distinguishes between two kinds of casting:
Implicit casting happens automatically, without you writing any conversion code. Python only does this in a very limited, safe situation: mixed-type numeric arithmetic. When you add an int and a float, Python automatically widens the int to a float before adding, because doing so never loses information. Python will not implicitly convert a string to a number, or a number to a string — those always require an explicit cast, and attempting to skip one (for example "5" + 3) raises a TypeError.
Explicit casting is when you call a constructor function yourself — int(x), float(x), str(x), bool(x), list(x), tuple(x), set(x), dict(x) — to convert a value on purpose. This is what people usually mean by “type casting” in Python, and it’s necessary whenever Python won’t convert for you, such as turning a string typed by a user into a number you can do arithmetic with.
Under the hood these constructor functions are not magic: int, float, and str are the type objects themselves, and calling one invokes that type’s constructor. Built-in constructors know how to build a new instance from several kinds of source data (a string of digits, another number, a boolean, or any object that implements the right special method — covered in the “Under the Hood” section below).
Syntax
The general form of an explicit cast is simply calling the target type as a function with the value you want to convert:
raw_value = "123"
converted_value = int(raw_value)
print(converted_value)
print(type(converted_value))
Output:
123
<class 'int'>
| Function | Converts to | Example |
|---|---|---|
int(x) |
Integer | int("10") → 10 |
float(x) |
Floating-point number | float("3.14") → 3.14 |
str(x) |
String | str(42) → "42" |
bool(x) |
Boolean | bool(0) → False |
list(x) |
List | list("ab") → ['a', 'b'] |
tuple(x) |
Tuple | tuple([1, 2]) → (1, 2) |
set(x) |
Set | set([1, 1, 2]) → {1, 2} |
dict(x) |
Dictionary | dict([("a", 1)]) → {'a': 1} |
Examples
Example 1: Basic conversions
age_str = "25"
age_int = int(age_str)
height_str = "5.9"
height_float = float(height_str)
is_active = 1
is_active_bool = bool(is_active)
print(age_int, type(age_int))
print(height_float, type(height_float))
print(is_active_bool, type(is_active_bool))
print(age_int + 5)
Output:
25 <class 'int'>
5.9 <class 'float'>
True <class 'bool'>
30
Here age_str and height_str start as plain text. Calling int() and float() parses the digits inside those strings and produces real numeric objects, which is why age_int + 5 works — you cannot add an integer to a string. Passing the integer 1 to bool() yields True, because bool() treats any nonzero number as truthy.
Example 2: Truncation vs. rounding
price_per_item = 4.5
quantity = 7
total = price_per_item * quantity
print(f"Total: {total}")
whole_dollars = int(total)
print(f"Whole dollars: {whole_dollars}")
rounded = round(total)
print(f"Rounded: {rounded}")
negative = -7.8
print(int(negative))
Output:
Total: 31.5
Whole dollars: 31
Rounded: 32
-7
This example highlights a critical detail: int() does not round a float, it truncates — it simply chops off everything after the decimal point, moving toward zero. That is why int(31.5) gives 31, not 32, and why int(-7.8) gives -7 (toward zero), not -8 (which is what flooring would give). If you actually want mathematical rounding, use the built-in round() function instead, which uses “round half to even” (banker’s rounding) — that’s why round(31.5) becomes 32 here.
Example 3: Casting between collections
def parse_scores(raw_scores: str) -> list[int]:
return [int(score.strip()) for score in raw_scores.split(",")]
raw = "88, 92, 79, 100, 92"
scores = parse_scores(raw)
print(scores)
print(sum(scores) / len(scores))
unique_scores = sorted(set(scores))
print(unique_scores)
scores_tuple = tuple(scores)
print(scores_tuple)
Output:
[88, 92, 79, 100, 92]
90.2
[79, 88, 92, 100]
(88, 92, 79, 100, 92)
This is a more realistic scenario: raw text (perhaps read from a file or a form field) is split on commas, each piece is stripped of surrounding whitespace, and cast to int inside a list comprehension. Once you have a list of numbers, casting it to set() removes duplicates (the repeated 92 collapses to one), and casting it to tuple() produces an immutable, ordered copy that preserves every original value including the duplicate.
Under the Hood: Dunder Methods
When you call int(x), float(x), str(x), or bool(x), Python follows a fairly predictable procedure:
- Python checks the type of
x. Ifxis already the target type, a new object with the same value is typically returned (since these built-in types are immutable, this behaves like a cheap copy). - If
xis a string being cast withint()orfloat(), Python parses the string’s characters as a numeric literal (optionally with leading/trailing whitespace and a sign). If the text doesn’t form a valid number, Python raisesValueError. - If
xis a float being cast withint(), Python truncates toward zero rather than rounding, as shown above. - If
xis an instance of a custom class, Python looks for a special (“dunder”) method on that class:__int__forint(),__float__forfloat(),__str__forstr(), and__bool__forbool()(falling back to__len__— a nonzero length means truthy — if__bool__is absent).
You can see this in action by defining those methods yourself:
class Temperature:
def __init__(self, celsius: float):
self.celsius = celsius
def __int__(self) -> int:
return int(self.celsius)
def __float__(self) -> float:
return float(self.celsius)
def __str__(self) -> str:
return f"{self.celsius}°C"
temp = Temperature(36.6)
print(int(temp))
print(float(temp))
print(str(temp))
print(f"Body temperature is {temp}")
Output:
36
36.6
36.6°C
Body temperature is 36.6°C
Calling int(temp) doesn’t know how to convert a Temperature object on its own — it delegates to Temperature.__int__, which we defined to truncate the stored Celsius value. The same happens for float() and str(). The final f-string doesn’t call str() directly, but formatting an object with no format spec falls back to its __str__ result, so the output matches.
Common Mistakes
Mistake 1: Casting a non-numeric string directly with int()
A very common error is trying to cast a string like "42.5" straight to int. int() only understands whole-number text (like "42") — it does not parse decimal points, so it raises ValueError instead of quietly dropping the fraction.
try:
value = int("42.5")
except ValueError as e:
print(f"Conversion failed: {e}")
value = int(float("42.5"))
print(value)
Output:
Conversion failed: invalid literal for int() with base 10: '42.5'
42
The fix is to convert through float() first (which does understand decimal points), and only then to int() if you want to discard the fraction, as shown on the last two lines.
Mistake 2: Assuming bool() only cares about “true-sounding” values
New Python programmers often expect strings like "False" or "0" to convert to the boolean False. They don’t — bool() on a string only checks whether the string is empty, not what it says.
values = [0, 1, -1, 0.0, "", "False", "0", [], [0], None]
for v in values:
print(repr(v), "->", bool(v))
Output:
0 -> False
1 -> True
-1 -> True
0.0 -> False
'' -> False
'False' -> True
'0' -> True
[] -> False
[0] -> True
None -> False
Every value here is truthy except 0, 0.0, the empty string, the empty list, and None. The strings "False" and "0" are both non-empty, so both are truthy — a frequent source of bugs when checking user-submitted form data.
Best Practices
- Wrap explicit casts of user input or external data (files, APIs, forms) in
try/except ValueErrorso bad input doesn’t crash your program. - Use
round()when you want mathematical rounding; don’t rely onint()to round, since it always truncates toward zero. - When parsing a string that might contain a decimal point, cast through
float()first, then toint()only if you intentionally want to discard the fraction. - Remember that casting to
set()deduplicates and casting todict()requires key-value pairs — only use these casts when that behavior is actually what you want. - Add type hints at cast boundaries (for example
def parse(raw: str) -> int:) so readers immediately see what type flows in and out of a function that performs conversion. - Avoid casting just to silence a linter or type checker; if two values genuinely have mismatched types, fix the underlying design rather than papering over it with a cast.
- When testing truthiness, prefer explicit comparisons (
if count == 0:) over relying on implicitbool()conversion when the intent might not be obvious to a reader.
Practice Exercises
- Exercise 1: Write a function
total_price(price_str, quantity_str)that accepts two strings representing a price and a quantity, casts them to the appropriate numeric types, and returns the total cost rounded to 2 decimal places. - Exercise 2: Given
mixed = ["3", "7", "12", "abc", "9"], write code that builds a new list containing only the elements that can be successfully cast toint, skipping any that raiseValueError. The expected result is[3, 7, 12, 9]. - Exercise 3: Before running any code, predict the results of
bool("0"),bool(0.0),bool(" "), andint(" 42 "). Then explain, in your own words, why each result is what it is.
Summary
- Type casting converts a value to a different type and always produces a new object; it never mutates the original.
- Python performs implicit casting only for safe numeric widening, such as combining an
intwith afloat. - Explicit casting uses constructor functions:
int(),float(),str(),bool(),list(),tuple(),set(), anddict(). int()truncates floats toward zero rather than rounding — useround()when you need actual rounding.- Casting an invalid numeric string with
int()orfloat()raisesValueError, so validate or handle it withtry/except. - Custom classes can support casting by implementing
__int__,__float__,__str__, and__bool__. - Almost every value in Python is truthy; only
0,0.0, empty strings/collections,None, andFalseitself are falsy.
