Real programs constantly move data between types. A price comes in from a form as a string, and you need a number to do math on it. A list of products needs to become a set to remove duplicates. A boolean check decides whether to show a message. This lesson covers the conversion functions Python gives you, when Python converts automatically, and what happens when a conversion can't succeed.
Python has two ways to move a value from one type to another:
int and a float in an expression makes the result a float.int(), float(), str(), bool(), list(), tuple(), set(), or dict().The distinction matters because implicit conversion is rare in Python. The language prefers to make you ask. That's why 1 + "2" raises an error in Python where it might "just work" in JavaScript or PHP.
Here Python implicitly promoted quantity (an int) to a float so it could multiply with price. The result is a float. We didn't write float(quantity) anywhere; the interpreter did it for us because mixing int and float in arithmetic is safe and unambiguous.
Now compare that to mixing a string and a number:
What's wrong with this code?
price is a string, not a number. "19.99" * 3 is valid Python, but it means "repeat the string three times", not "multiply 19.99 by 3". The fix is explicit conversion:
This is the rule of thumb: when the types involved are different in a way that could mean two things, Python won't guess. You convert.
intint(x) turns x into a whole number. It accepts numbers, strings that look like integers, and booleans.
A few things to notice:
int(3.7) is 3, not 4. int(-3.7) is -3, not -4. Python drops the fractional part and moves toward zero. For rounding, use round() instead.int(" 42 ") works.True is 1, False is 0. This makes sense because bool is technically a subclass of int in Python.The rounding distinction matters more than people expect:
int() always moves toward zero. round() follows standard rounding rules.
Strings have to look like valid integers. Anything else raises ValueError:
Even though 3.7 would convert fine if it were a number, int() won't parse a string that contains a decimal point. To go from a decimal string to an integer, do it in two steps: int(float("3.7")) gives 3.
int() takes a second argument: the base of the number in the string. This is useful for hex (16), octal (8), or binary (2) inputs.
int("12", 16) reads "12" as a base-16 (hex) number, which equals 18 in decimal. The 0x, 0o, and 0b prefixes are optional; int() recognizes them.
The base argument only works with string input. int(12, 16) raises TypeError because 12 is already a number, so there's no string to parse.
Cost: int() on a string is O(n) where n is the length of the string. For typical inputs this is irrelevant, but parsing millions of values in a loop adds up.
floatfloat(x) turns x into a decimal number. It accepts numbers, strings, and booleans.
Notes:
int converts cleanly. 42 becomes 42.0."19.99" parses as you'd expect."1e3" is 1 x 10^3, which is 1000.0.True and False become 1.0 and 0.0.Two special string values are also legal:
inf is positive infinity, -inf is negative infinity, and nan is "not a number". You won't use these often, but they're worth knowing exist.
Like int(), bad strings raise ValueError:
A common e-commerce use is parsing a price string from a form or a CSV file:
Without float(), form_input * quantity would have repeated the string three times, giving "29.9929.9929.99".
strstr(x) turns anything into a string. There is no case where str() raises an error on a built-in type. Every Python object has a string representation, even None.
The most common use is building messages that mix text and numbers. You can use str() directly, but f-strings are almost always cleaner:
The f-string version doesn't call str() explicitly. It does it internally. F-strings handle the conversion for you and read more naturally.
Cost: Concatenating strings with + creates a new string every time. For two or three pieces it doesn't matter. For dozens or hundreds, use "".join(parts) or an f-string, both of which allocate only once.
boolbool(x) follows Python's falsiness rules. Anything in the "falsy" set becomes False. Everything else becomes True.
The falsy values are:
| Type | Falsy values |
|---|---|
| Numbers | 0, 0.0, 0j |
| Strings | "" |
| Lists | [] |
| Tuples | () |
| Sets | set() |
| Dicts | {} |
| Special | None, False |
Everything else is truthy:
Two of these surprise beginners:
bool(" ") is True because the string contains a space character, which is not the same as an empty string.bool([0]) is True because the list has one element, even though that element happens to be 0. The truthiness check is "is this collection empty?", not "are its contents falsy?".You rarely call bool() directly. Python applies it automatically when you write if x: or while x: or any boolean context. Knowing the rules helps debug "why did my if-statement go the wrong way?" moments.
if cart: is equivalent to if bool(cart):, which is if False: because the list is empty. This idiom (if some_collection:) is the Pythonic way to check whether a collection has anything in it. Don't write if len(cart) > 0: unless you need the count anyway.
list, tuple, and setThese three functions all accept an iterable: anything you can loop over. That includes lists, tuples, sets, strings, dictionary keys, generators, and more. The output is a new collection of the requested type.
The main reasons to convert between these three:
A common pattern: deduplicate a list of products while preserving order.
set() deduplicates, but it doesn't preserve order in the way most people expect. If you need both deduplication and original order, use dict.fromkeys():
This works because regular dictionaries preserve insertion order (since Python 3.7), and dict.fromkeys() builds a dictionary using the iterable's values as keys (which dedupes).
Converting in the other direction is just as easy:
You can't append to a tuple directly because tuples are immutable. Converting to a list, modifying, and (if needed) converting back is the standard workaround.
A diagram of how these conversions relate:
The diagram shows that all three constructors accept the same kind of input (any iterable) but produce containers with different properties. Pick the one whose properties match what you actually need.
Cost: list(some_set) and set(some_list) are O(n) where n is the length of the source. They walk the whole input. Don't convert in a tight loop if one type would do the whole job.
dictdict() builds a dictionary. It accepts several different shapes of input, which trip up beginners.
The two most common ways:
This keyword-argument form is the most readable when you know the keys at write time. It only works when the keys are valid Python identifiers (no spaces, doesn't start with a digit).
The other common form takes an iterable of key-value pairs:
Each element of the iterable must be a two-element sequence. The first becomes the key, the second becomes the value. This form is useful when the pairs come from another data source (a CSV reader, a zip() of two lists, an API response).
A real-world pattern: converting two parallel lists into a dictionary.
zip() pairs up the two lists into tuples like ("Wireless Mouse", 19.99). dict() then turns those pairs into key-value entries.
You can also copy an existing dict:
dict(default_settings) creates a shallow copy, so modifying user_settings doesn't affect the original.
You've seen ValueError from int("abc") already. Here's the broader picture: each conversion function has its own failure modes.
The traceback's last line is the part that matters. ValueError means the function recognized the input type (it's a string), but the value isn't something it knows how to convert.
There's also TypeError, which happens when the input isn't a type the function accepts at all:
The message depends on the function and how it's called, but the type is the giveaway. TypeError means "wrong shape of input", ValueError means "right shape, wrong value".
A summary table:
| Function | Common error | Typical cause |
|---|---|---|
int(x) | ValueError | Non-numeric string like "abc" or "3.7" |
float(x) | ValueError | Non-numeric string like "abc" |
str(x) | (never raises on built-ins) | N/A |
bool(x) | (never raises on built-ins) | N/A |
list(x) | TypeError | x isn't iterable, like list(42) |
tuple(x) | TypeError | x isn't iterable |
set(x) | TypeError | x isn't iterable, or contains unhashable elements |
dict(x) | ValueError / TypeError | x isn't pairs of key-value |
You'll handle these failures gracefully using try / except. The shape looks like this:
try runs the risky code. If it raises ValueError, control jumps to the except block. If everything succeeds, the except block is skipped. For now, recognize the pattern and know it exists.
A diagram of the conversion lifecycle:
The check happens in two stages. First, is the input even something this function can work with? If you pass a list to int(), you'll hit TypeError. Second, given that the type is OK, can the actual value be converted? If you pass "abc" to int(), the string is the right type but the value isn't a number, so you get ValueError.
Implicit conversion in Python is narrow. It happens in arithmetic between numeric types, and that's mostly it.
Rules:
int mixed with int stays int.int mixed with float promotes to float.bool mixed with int or float promotes to int or float (since True is 1 and False is 0).Notice that True + 1 is 2, not True. As soon as you do arithmetic on a bool, you're working with integers. This is occasionally useful: sum([True, False, True, True]) gives you the count of True values, which is 3.
What Python won't do implicitly: convert strings to numbers, or numbers to strings, in arithmetic. Those have to be explicit.
The fix is "Price: $" + str(19.99) or, much better, an f-string: f"Price: ${19.99}".
Division has one more quirk worth knowing. The / operator always produces a float, even when both operands are integers and the result is whole. The // operator does integer division (truncating toward negative infinity).
If you want an integer back from /, you have to convert explicitly: int(10 / 2). Or just use //.
10 quizzes