Python 3.12+ · Updated November 2026 · Every program explained line by line

Python Type Casting
Convert values safely and predictably

A complete, runnable reference for converting between Python's built-in types. Every code block includes a numbered line-by-line breakdown, so you understand exactly what happens at each step — plus practice exercises, pitfalls to avoid, and a full quick-reference table.

# A first look at Python type casting
user_input = "42"
age = int(user_input)

price = "19.99"
cost = float(price)

total = 3 * cost
label = "Total: $" + str(round(total, 2))

is_adult = bool(age >= 18)
items = list("abc")

print(f"age={age} ({type(age).__name__})")
print(f"cost={cost} ({type(cost).__name__})")
print(label)
print(f"is_adult={is_adult}  items={items}")
Written & reviewed by Glenn Junsay Pansensoy Last reviewed: November 2026 Python 3.12+ ~25 min read

🧭 Why Type Casting Matters

Real programs constantly convert between types — from user input, files, APIs, and databases

Python is a strongly typed language. It will not silently coerce a string "42" into an integer when you try to do arithmetic on it. This is a feature — it prevents whole classes of bugs — but it also means that the moment your data crosses a boundary (from a text file, a web form, a JSON response), you need to convert it explicitly. Type casting is how you do that safely.

Three places you cast constantly

User input: input() always returns a string — you must cast before doing math.
Files & APIs: JSON and CSV parsers give you strings and numbers mixed — cast as needed.
Formatting output: str() and f-strings turn any value into text for display.

⚖️ Implicit vs Explicit Conversion

Python automatically promotes some types; you must convert others yourself

🤖 Implicit Conversion — Python Does It

When two numeric types meet in an operation, Python promotes the narrower one to the wider one. int + float → float. There is no data loss, so Python does it silently.

a = 5
b = 2.5
result = a + b

print(f"result = {result}")
print(f"type = {type(result).__name__}")

whole = 5 + 3
print(f"whole = {whole}  type = {type(whole).__name__}")

flag = True + 1
print(f"flag = {flag}  type = {type(flag).__name__}")
Line-by-line explanation
  1. 1–2Two values: a is an int (5), b is a float (2.5).
  2. 3a + b — Python detects the type mismatch and implicitly promotes a to 5.0 before the addition. The result is a float.
  3. 5–6The first pair of prints confirms result is 7.5 and its type is "float".
  4. 8–95 + 3 — both operands are int, so no promotion happens. Result is the int 8.
  5. 11–12True + 1 — because bool is a subclass of int, Python treats True as 1. Result is the int 2.
Expected outputresult = 7.5 type = float whole = 8 type = int flag = 2 type = int
💡 Implicit conversion only works between numeric types. Python will never implicitly convert a str to a number — that's why "5" + 3 raises TypeError.

🎯 Explicit Conversion — You Do It

When implicit rules don't apply (or you want a specific type), you call a conversion function explicitly. This is casting — and it puts you in control of what happens on failure.

text = "42"
number = int(text)

print(f"text={text!r} type={type(text).__name__}")
print(f"number={number!r} type={type(number).__name__}")

back_to_text = str(number)
print(f"back_to_text={back_to_text!r}")

try:
    bad = int("hello")
except ValueError as e:
    print(f"Failed: {e}")
Line-by-line explanation
  1. 1text = "42" — a string. Arithmetic on it directly would fail.
  2. 2number = int(text) — explicit cast. Python parses the string as an integer literal and returns the int 42.
  3. 4–5Confirms the original text is still a string and number is now an int.
  4. 7str(number) — explicit cast back to string, producing "42".
  5. 10–12Attempting to cast "hello" raises ValueError. The try/except catches it so the program keeps running.
💡 Explicit casts almost always raise an exception on failure (ValueError or TypeError). Wrap user-supplied input in try/except — never assume it will succeed.

🔢 Converting to int — int()

Truncates floats, parses strings, honours base prefixes

📥 int() — Float, String, and Base Conversions

int() behaves differently depending on its input. Floats are truncated toward zero (not rounded). Strings are parsed as integer literals. An optional second argument sets the base for string parsing.

print(int(3.99))
print(int(-3.99))
print(int("42"))
print(int("  -17  "))
print(int("0b101010", base=0))
print(int("0x2A", base=0))
print(int("101010", base=2))
print(int("52", base=8))
print(int("2A", base=16))
Line-by-line explanation
  1. 1int(3.99) → 3. The fractional part is truncated, not rounded. int() never rounds.
  2. 2int(-3.99) → -3. Truncation always moves toward zero, so negative floats round up numerically.
  3. 3int("42") → 42. Parses a decimal integer literal.
  4. 4int(" -17 ") → -17. Leading and trailing whitespace is stripped before parsing.
  5. 5–6base=0 tells Python to infer the base from the prefix: 0b means binary, 0x means hex. Both return 42.
  6. 7–9Explicit base arguments parse without a prefix. int("101010", 2) = 42, int("52", 8) = 42, int("2A", 16) = 42.
Expected output3 -3 42 -17 42 42 42 42 42
💡 If you need true rounding, use round() first: int(round(3.99)) gives 4. But note that round() uses banker's rounding — round(2.5) is 2, not 3.

⚠️ What Raises an Error

Some strings look numeric but aren't valid integer literals. Knowing which cases fail is half the battle.

for s in ["3.14", "abc", "", "1,000"]:
    try:
        print(int(s))
    except ValueError as e:
        print(f"int({s!r}) failed: {e}")
Line-by-line explanation
  1. 1Iterates four problematic strings.
  2. 2–3Attempts the cast. If it succeeds, prints the result.
  3. 4–5If it fails, catches ValueError and prints the error message. The !r in the f-string shows the string's repr, so "" appears as '' (distinguishing empty from space).
Expected outputint('3.14') failed: invalid literal for int() with base 10: '3.14' int('abc') failed: invalid literal for int() with base 10: 'abc' int('') failed: invalid literal for int() with base 10: '' int('1,000') failed: invalid literal for int() with base 10: '1,000'
💡 To convert a float-string like "3.14" to int, chain casts: int(float("3.14")) → 3. For thousands separators, strip commas first: int("1,000".replace(",", "")).

🔣 Converting to float — float()

Handles decimal strings, scientific notation, and special values

📥 float() — Integers, Strings, and Scientific Notation

float() converts any numeric type or numeric string to a float. It also recognises inf, -inf, and nan (case-insensitive).

print(float(7))
print(float("3.14"))
print(float("-0.5"))
print(float("1e3"))
print(float("1.5e-2"))
print(float("inf"))
print(float("nan"))

import math
print(math.isnan(float("nan")))
print(math.isinf(float("inf")))
Line-by-line explanation
  1. 1float(7) → 7.0. An integer becomes a float with no data loss.
  2. 2float("3.14") → 3.14. Parses the decimal string.
  3. 3float("-0.5") → -0.5. Negative decimals are fine.
  4. 4float("1e3") → 1000.0. Scientific notation is supported.
  5. 5float("1.5e-2") → 0.015. Small numbers via negative exponent.
  6. 6–7inf and nan are recognised as special float values — not errors.
  7. 9–10Use math.isnan() and math.isinf() to test for them. Never compare with == — nan != nan by definition.
💡 Never test for nan with ==. Because NaN is "not a number", nan == nan is always False. Use math.isnan(x).

📝 Converting to str — str()

Every Python value has a string representation — but str() and repr() differ

📤 str() vs repr()

str() gives the human-readable form; repr() gives the developer-readable form — usually a valid Python expression that recreates the value. Use str() for display, repr() for debugging.

print(str(42))
print(str(3.14))
print(str(True))
print(str(None))
print(str([1, 2, 3]))
print(str({"a": 1}))

print(repr("hello"))
print(str("hello"))
print(repr("a\nb"))
print(str("a\nb"))
Line-by-line explanation
  1. 1–6Each str() call returns the human-readable form. Lists and dicts keep their Python syntax — useful for logging but not for building other strings.
  2. 8repr("hello") → 'hello' — with quotes. This is exactly how you'd type the string in source code.
  3. 9str("hello") → hello — no quotes. Just the content.
  4. 10repr("a\nb") → 'a\nb' — the escape sequence is shown literally, so you can see the newline exists.
  5. 11str("a\nb") → two actual lines. The newline is rendered, not escaped.
💡 When building strings by concatenation with +, use str() for numbers, but prefer f-strings — they handle conversion automatically and are more readable: f"Score: {score}".

🔗 Concatenation and f-strings

The most common conversion in everyday code is turning non-string values into text for output. F-strings do this invisibly and are almost always the right choice.

count = 5
price = 9.99
name = "Ana"

old_style = "Name: " + name + ", total: " + str(count * price)
f_style = f"Name: {name}, total: {count * price}"

print(old_style)
print(f_style)
print(f_style == old_style)
Line-by-line explanation
  1. 1–3Three variables of three different types.
  2. 5Manual concatenation: "Name: " + name + ", total: " + str(...). Note the explicit str() — without it, Python raises TypeError because it refuses to concatenate a number onto a string.
  3. 6f-string version: any expression inside {} is converted automatically with str() internally.
  4. 8–10Both produce the same string. The final comparison prints True, confirming the two approaches are equivalent — but the f-string is far easier to read.

✅ Converting to bool — bool()

Truthiness rules apply — every value becomes either True or False

🧠 bool() and Truthiness

There's no way to go from "True" (the string) to True (the boolean) via bool() — because any non-empty string is truthy. Parsing user input like "false" requires explicit comparison.

print(bool(0))
print(bool(-1))
print(bool(0.0))
print(bool(""))
print(bool("0"))
print(bool("False"))
print(bool([]))
print(bool([0]))
print(bool(None))

user_input = "false"
parsed = user_input.strip().lower() == "true"
print(f"parsed = {parsed}")
Line-by-line explanation
  1. 1bool(0) → False. Zero is falsy for any numeric type.
  2. 2bool(-1) → True. Any non-zero number is truthy.
  3. 3–4bool(0.0) and bool("") → False. Zero float and empty string are falsy.
  4. 5–6bool("0") and bool("False") → both True. Any non-empty string is truthy, regardless of content.
  5. 7–8bool([]) → False (empty list). bool([0]) → True (list contains one item, even if it's zero).
  6. 9bool(None) → False. None is always falsy.
  7. 11–12To parse the text "false" into the boolean False, compare explicitly against "true". strip() removes whitespace, lower() makes the comparison case-insensitive.
💡 For config files and API flags, use a mapping: {"true": True, "false": False, "1": True, "0": False}. Explicit lookup makes intent clear and avoids accidental truthiness bugs.

📦 Collection Conversions — list, tuple, set, dict

Convert between sequence types freely — but understand what is preserved and what is lost

🔁 list(), tuple(), set(), dict()

Any iterable can be converted to any collection type. The conversion processes each element once — order, uniqueness, and mutability are determined by the target type.

text = "hello"
numbers = [1, 2, 2, 3, 3, 3]

print(list(text))
print(tuple(text))
print(set(text))

print(list(numbers))
print(set(numbers))
print(list(set(numbers)))

pairs = [("a", 1), ("b", 2)]
print(dict(pairs))
print(dict(name="Ana", age=25))
Line-by-line explanation
  1. 1–2A string and a list with duplicates — both are iterables.
  2. 4list("hello") → ['h', 'e', 'l', 'l', 'o']. Each character becomes an element.
  3. 5tuple(text) → ('h', 'e', 'l', 'l', 'o'). Same content, immutable container.
  4. 6set(text) → {'h', 'e', 'l', 'o'}. Duplicates are removed and order is lost.
  5. 8list(numbers) keeps duplicates intact.
  6. 9set(numbers) reduces to {1, 2, 3} — the classic deduplication trick.
  7. 10list(set(numbers)) converts back to a list — but order is not guaranteed.
  8. 12–13dict(pairs) turns a list of 2-tuples into a dict: {'a': 1, 'b': 2}.
  9. 14dict(name="Ana", age=25) builds a dict from keyword arguments — a handy shortcut.
💡 To deduplicate and preserve order, use list(dict.fromkeys(seq)). That relies on dictionaries maintaining insertion order since Python 3.7 — unlike sets.

⚠️ Common Pitfalls to Avoid

The four mistakes that cause most type-casting bugs in real programs

❌ Pitfall 1 — Using input() Without Casting

input() always returns a string, even if the user types a number. Arithmetic on it fails with TypeError.

user_input = "42"

result = user_input + 1
Line-by-line explanation
  1. 1Simulates what input() returns — a string, even though it looks like a number.
  2. 3user_input + 1 raises TypeError: can only concatenate str (not "int") to str. This is one of the most common beginner mistakes.
💡 The fix: result = int(user_input) + 1. Always cast input() before arithmetic.

❌ Pitfall 2 — Using == Instead of Explicit Comparison for bool

Because bool("False") is True, casting strings to booleans rarely does what you mean.

value = "False"

print(bool(value))

parsed = value.strip().lower() == "true"
print(parsed)
Line-by-line explanation
  1. 1The string "False" — five characters, non-empty.
  2. 3bool("False") → True. Surprising but correct — any non-empty string is truthy.
  3. 5Explicit comparison against the literal "true" gives the expected result: False.

❌ Pitfall 3 — Assuming int() Rounds

int() truncates toward zero. It never rounds to nearest. If you need rounding, call round() first.

print(int(2.9))
print(int(-2.9))
print(round(2.9))
print(round(-2.9))
Line-by-line explanation
  1. 1int(2.9) → 2. Truncation, not rounding.
  2. 2int(-2.9) → -2. Toward zero, not toward negative infinity.
  3. 3–4round(2.9) → 3, round(-2.9) → -3. Rounding to nearest.
💡 If you need "round then int", the order matters: int(round(2.9)) gives 3, but round(int(2.9)) gives 2.

❌ Pitfall 4 — Ignoring Exceptions on Bad Input

Real user input is messy. Any cast that could fail should be guarded. Otherwise one bad character crashes the whole program.

def parse_int(text, default=0):
    try:
        return int(text)
    except (ValueError, TypeError):
        return default

print(parse_int("42"))
print(parse_int("abc"))
print(parse_int(None, default=-1))
Line-by-line explanation
  1. 1Defines a function that takes a value and a fallback default.
  2. 2–3Tries the cast. On success, returns the integer.
  3. 4–5Catches two exceptions: ValueError for malformed strings, TypeError for values that aren't strings or numbers at all (like None). Returns the default in either case.
  4. 7Valid input: returns 42.
  5. 8Invalid string: returns 0 (the default).
  6. 9Unsupported type: returns -1 (explicit default).

🛡️ Safe Conversion — Building Robust Helpers

A small set of well-designed helper functions eliminates most casting bugs

🧰 A Reusable Parsing Module

Once you find yourself writing try/except around int() three times in a project, it's time to extract helpers. Here's a minimal, production-shaped set.

def parse_int(value, default=0):
    try:
        return int(value)
    except (ValueError, TypeError):
        return default

def parse_float(value, default=0.0):
    try:
        return float(value)
    except (ValueError, TypeError):
        return default

def parse_bool(value, default=False):
    if isinstance(value, bool):
        return value
    if isinstance(value, str):
        v = value.strip().lower()
        if v in ("true", "1", "yes"):
            return True
        if v in ("false", "0", "no"):
            return False
    return default

print(parse_int("42"), parse_int("bad", -1))
print(parse_float("3.14"), parse_float(None))
print(parse_bool("YES"), parse_bool("no"), parse_bool(True))
Line-by-line explanation
  1. 1–5parse_int: tries the cast, returns the value on success. On failure (bad string or unsupported type), returns default. Catching both ValueError and TypeError is what makes it actually safe.
  2. 7–11parse_float: same pattern, default 0.0.
  3. 13–22parse_bool: more nuanced — bool() alone is dangerous for strings. First handles actual booleans. For strings, normalises case and whitespace, then looks up in two explicit sets of recognised truthy and falsy tokens. Falls back to default for unrecognised input.
  4. 24–26Test calls that exercise the success path, the failure path, and the string-to-bool path for each helper.
💡 Design principle: helper functions should never raise on bad input — they should return a sensible default. That way any caller can use them without thinking about exceptions. If you need exceptions, use the raw cast functions directly.

🏋️ Practice — Check Your Understanding

Three exercises with solutions. Try each before expanding the answer.

Exercise 1 — Predict the Output

What does each line print? Write your answers before expanding.

print(int("3.14"))
print(int(3.99))
print(int(float("3.14")))
print(bool("False"))
print(str(None))
print(list("abc"))
print(set([1, 1, 2, 2]))
Show solution

Line 1: ValueError — "3.14" is not a valid integer literal.
Line 2: 3 — truncation, not rounding.
Line 3: 3 — chained cast works because float("3.14") succeeds first.
Line 4: True — any non-empty string is truthy.
Line 5: "None" — string representation of the None object.
Line 6: ['a', 'b', 'c'] — a string iterates character-by-character.
Line 7: {1, 2} — sets remove duplicates.

Exercise 2 — Fix the Runtime Error

This program crashes on the second line. Explain why and fix it.

user_input = "25"
next_age = user_input + 1
print(next_age)
Show solution

user_input is a string, so user_input + 1 raises TypeError. Python will not silently coerce. Fix: next_age = int(user_input) + 1. If the input might not be a valid integer, wrap it: next_age = int(user_input) + 1 inside a try/except — or use the parse_int helper from the Safe Conversion section.

Exercise 3 — Build a Temperature Converter

Write a function celsius_from_fahrenheit that accepts a value that might be a string like "98.6", a float, or an int — and returns the Celsius equivalent rounded to one decimal place. If the value can't be parsed, return None.

Show solution
def celsius_from_fahrenheit(value):
    try:
        f = float(value)
    except (ValueError, TypeError):
        return None
    return round((f - 32) * 5 / 9, 1)

print(celsius_from_fahrenheit("98.6"))
print(celsius_from_fahrenheit(212))
print(celsius_from_fahrenheit("abc"))

Key points: float() handles strings, ints, and floats uniformly, so one cast covers all three input types. The try/except catches both bad strings (ValueError) and unsupported types (TypeError). Returning None for bad input lets callers decide what to do next. Expected output: 37.0, 100.0, None.

❓ Frequently Asked Questions

Common questions about Python type casting, answered directly

Implicit conversion is done automatically by Python's interpreter — like when an int and a float are mixed in arithmetic and Python promotes the int to float. Explicit conversion (casting) requires you to call a conversion function like int(), float(), or str() to force a specific type. Implicit conversion never loses information; explicit casting can (int(3.99) truncates to 3).

int() of a string expects a valid integer literal, and "3.14" contains a decimal point, so parsing fails with ValueError. int() of a float truncates the decimal portion. To convert "3.14" to an integer, chain two calls: int(float("3.14")) returns 3.

Wrap the conversion in try/except and catch ValueError for malformed input and TypeError for unsupported types. For example: try: n = int(user_input) except ValueError: n = 0 (or prompt again). Never trust input() to produce a number — it always returns a string, and users can enter anything.

No. bool("False") returns True because any non-empty string is truthy. To parse the word "False" into the boolean False, compare explicitly: value = user_input.lower() == "true" — or use a mapping like {"true": True, "false": False}.

📋 Quick-Reference Table

Every conversion function and its common cases — bookmark this page for quick reference

Call Result Notes
int(3.99)3Truncates toward zero — never rounds
int(-3.99)-3Negative truncation also moves toward zero
int("42")42Parses a decimal integer literal
int(" -17 ")-17Whitespace is stripped
int("3.14")ValueErrorNot a valid integer literal — chain with float()
int("0x2A", 0)42base=0 auto-detects prefix
int("2A", 16)42Explicit base for hex
float(7)7.0int → float, no data loss
float("3.14")3.14Parses decimal string
float("1e3")1000.0Scientific notation supported
float("nan")nanTest with math.isnan() — not ==
str(42)"42"Human-readable form
repr("hello")'hello'Developer-readable form with quotes
bool(0)FalseZero is falsy
bool("0")TrueNon-empty string is truthy — surprise!
bool([])FalseEmpty containers are falsy
list("abc")['a', 'b', 'c']Iterates character-by-character
set([1, 2, 2, 3]){1, 2, 3}Removes duplicates
tuple([1, 2, 3])(1, 2, 3)Immutable copy
dict([("a", 1), ("b", 2)]){"a": 1, "b": 2}Builds from key-value pairs
Review Python data types

Educational Use Disclaimer

This page is part of Poetic Bytes, a free educational resource for learning Python programming. The code examples are provided for instruction and demonstration. They are not production-audited and should not be used as-is in security-sensitive, financial, or safety-critical systems without independent review. See our full disclaimer and terms of use.

🗣️ Learner Voices

No comments yet — your voice starts the conversation.