Type Conversion in Python
Python converts numeric types automatically inside an expression and refuses everything else. Explicit conversion is a set of constructor functions, each with rules worth knowing.
- Basics
- Data Types
- Operators
- Strings
- Control Flow
- Lists
- Tuples
- Sets
- Dictionaries
- Comprehensions
- Functions
- Advanced Functions
- Recursion
- Exception Handling
- File Handling
- Modules
- Standard Library
- OOP
- Advanced OOP
- Iterators and Generators
- Decorators
- Context Managers
- Descriptors and Dataclasses
- Python Internals
- Concurrency
- Regular Expressions
- Serialization
- Command Line Python
- Testing and Debugging
- Type Hints
- Performance
- Python Security
- DSA with Python
Two kinds of conversion
- Implicit - Python does it for you, silently, and only when no information is lost.
- Explicit - you call a conversion function and take responsibility for the result.
Implicit conversion
Implicit conversion in Python is limited almost entirely to numbers. Inside one arithmetic expression, the narrower type is promoted to the wider one.
bool -> int -> float -> complexprint(True + 1) # 2 bool promoted to int
print(3 + 2.5) # 5.5 int promoted to float
print(2.0 + 3j) # (2+3j) float promoted to complex
print(type(4 + 0.0)) # <class 'float'>That is the whole list. Python does not convert between text and numbers implicitly, and that refusal is deliberate:
print("3" + 4) # TypeError: can only concatenate str (not "int") to strIn a weakly typed language this might produce "34" or 7, and you would not know which until it went wrong in production. Python makes you say what you meant.
Explicit conversion
int()
print(int("42")) # 42
print(int(" 42 ")) # 42, surrounding whitespace is ignored
print(int(9.99)) # 9, truncates towards zero, never rounds
print(int(-9.99)) # -9
print(int(True)) # 1
print(int("1010", 2)) # 10, parsed as binary
print(int("ff", 16)) # 255
# print(int("42.0")) # ValueError: it is not an integer literal
# print(int("abc")) # ValueError
# print(int(None)) # TypeErrorNote the difference betweenValueErrorandTypeError. The right type with unusable contents givesValueError; the wrong type entirely givesTypeError. Reading that distinction saves debugging time.
float()
print(float("3.14")) # 3.14
print(float("42")) # 42.0
print(float(7)) # 7.0
print(float("1e3")) # 1000.0
print(float("inf")) # inf
# print(float("abc")) # ValueErrorstr()
print(str(42)) # '42'
print(str(3.14)) # '3.14'
print(str([1, 2])) # '[1, 2]'
print(str(None)) # 'None'str() never fails; every object can produce some text form. Its partner is repr(), which aims at an unambiguous developer facing form:
value = "10"
print(str(value)) # 10
print(repr(value)) # '10' - the quotes tell you it is textbool()
Every object converts to a boolean. The falsy values are a short, closed list; everything else is truthy.
| Falsy | Truthy |
|---|---|
False, None | Any non zero number |
0, 0.0, 0j | Any non empty string, including "0" and "False" |
"" | Any non empty container |
[], (), {}, set() | Objects with no rule saying otherwise |
print(bool(0), bool(1)) # False True
print(bool(""), bool("False")) # False True <- note the second one
print(bool([]), bool([0])) # False True <- a list holding a falsy value is not emptyContainer conversions
print(list("abc")) # ['a', 'b', 'c']
print(tuple([1, 2, 3])) # (1, 2, 3)
print(set([1, 2, 2, 3])) # {1, 2, 3} - duplicates removed
print(list({"a": 1, "b": 2})) # ['a', 'b'] - the keys
pairs = [("a", 1), ("b", 2)]
print(dict(pairs)) # {'a': 1, 'b': 2}
print(dict(zip(["a", "b"], [1, 2]))) # {'a': 1, 'b': 2}dict() needs pairs. Passing a flat list raises an error, and zip is the usual way to build the pairs from two parallel sequences.
Converting user input safely
input() always returns a string, so conversion is unavoidable, and it can always fail.
raw = input("Enter your age: ")
try:
age = int(raw)
except ValueError:
print("That is not a whole number.")
else:
print("Next year you will be", age + 1)Never assume the conversion succeeds. A user typing twenty or pressing Enter on an empty line will crash an unguarded program.
Conversion that loses information
print(int(3.99)) # 3 the fraction is gone
print(list({3, 1, 2})) # order is arbitrary, not the insertion order
print(set([1, 1, 2])) # duplicates are gone permanently
print(int(float("3.7"))) # 3 two conversions, one of them lossyThe last line is the standard way to accept "3.7" as an integer: convert to float first, because int("3.7") raises.
Common mistakes
- Calling
int("3.7"). Convert throughfloatfirst. - Assuming
int()rounds. It truncates. Useround()if rounding is wanted. - Trusting
bool("False")to beFalse. Any non empty string is truthy. - Converting user input without a
tryblock. - Using
str()whererepr()would have shown the problem, particularly when debugging whitespace. - Converting a set to a list and expecting a predictable order.
Best practices
- Convert at the boundary: turn input into the right type as soon as it enters the program, and keep it that way.
- Wrap every conversion of external data in
tryandexcept ValueError. - Prefer f-strings to explicit
str()calls when building messages. - Use
repr()in debug output so quotes and whitespace are visible.
Practice
- Explain why
int("42 ")works butint("4 2")does not. - Write a loop that keeps asking for a number until a valid integer is entered.
- Predict the truthiness of each:
"","0",[],[[]],0.0,"None",{},{0: 0}. - Convert
["a", "b"]and[1, 2]into a single dictionary in one expression. - Explain the difference between
str(x)andrepr(x)for the value"5", and when each is the right choice.
Conclusion
Python converts numbers implicitly and nothing else. Every other conversion is a function call you write on purpose, it may lose information, and on external data it may fail. Convert once, at the edge of your program, and check the result.