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. If x is 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 x is a string being cast with int() or float(), 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 raises ValueError.
  • If x is a float being cast with int(), Python truncates toward zero rather than rounding, as shown above.
  • If x is an instance of a custom class, Python looks for a special (“dunder”) method on that class: __int__ for int(), __float__ for float(), __str__ for str(), and __bool__ for bool() (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 ValueError so bad input doesn’t crash your program.
  • Use round() when you want mathematical rounding; don’t rely on int() to round, since it always truncates toward zero.
  • When parsing a string that might contain a decimal point, cast through float() first, then to int() only if you intentionally want to discard the fraction.
  • Remember that casting to set() deduplicates and casting to dict() 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 implicit bool() 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 to int, skipping any that raise ValueError. The expected result is [3, 7, 12, 9].
  • Exercise 3: Before running any code, predict the results of bool("0"), bool(0.0), bool(" "), and int(" 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 int with a float.
  • Explicit casting uses constructor functions: int(), float(), str(), bool(), list(), tuple(), set(), and dict().
  • int() truncates floats toward zero rather than rounding — use round() when you need actual rounding.
  • Casting an invalid numeric string with int() or float() raises ValueError, so validate or handle it with try/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, and False itself are falsy.