Type Conversion

Turning text into numbers and back, what converts silently, and what raises rather than guessing.

conversion.py

conversion.py Python 3
Output

                    

conversion_care.py

conversion_care.py Python 3
Output

                    

Worth knowing

The type names are the conversions: int(), float(), str(), list().
int("3.9") raises; int(3.9) truncates toward zero.
round(2.5) is 2, not 3 - it rounds to the nearest even on a tie.
bool("0") is True. Any non-empty string is.

Type Conversion: A Practical Guide

Python does not convert types behind your back. You ask, using the type name as a function, and it either succeeds or raises — it will not guess.

The constructors

int("42")    float("3.14")    str(42)    list("abc")    bool("")

Each type's name doubles as its conversion. That is why there is nothing to memorise beyond the types themselves.

Text in, text out

Anything read from a user, a file or a network arrives as text. "10" + "10" is "1010", and no error is raised, because concatenating strings is a perfectly sensible thing to do. Converting is on you:

int(text) + int(text)

This is the single most common source of confusion for beginners, and it is not really about conversion — it is about noticing that input is always a string.

Floats are not decimals

0.1 + 0.2 == 0.3  # False

Binary floating point cannot represent 0.1 exactly, so the sum is a hair off. This is not a Python quirk; it is how floats work everywhere. Compare with a tolerance, round before comparing, or use decimal.Decimal for money.

bool() is broader than it looks

bool("0") is True, because the string is not empty. Falsy values are: 0, 0.0, "", [], {}, set(), None and False. Everything else is truthy, including "False".

Conversion is not the same as checking

int("42") converts. isinstance(x, int) checks. Reaching for the wrong one is common, and the distinction matters because conversion can fail loudly while a check never does.

The Python habit is to attempt the conversion inside a try rather than to validate first with a regular expression or a series of isdigit calls. The attempt is the authoritative test - it knows about signs, whitespace, underscores in numeric literals and every other detail your hand-written check would miss.

def as_int(text, default=None):
    try:
        return int(text)
    except (TypeError, ValueError):
        return default

Catching both matters: ValueError for text that is not a number, TypeError for None or a list arriving where a string was expected.

The float trap

int("3.9") raises, which surprises people who expect it to truncate. The string is not an integer literal, so the conversion refuses rather than guessing. int(3.9) on an actual float does truncate, toward zero, so int(-3.9) is -3 rather than -4.

If you want rounding rather than truncation, round is the function, and it has its own surprise: it rounds halves to the nearest even number, so round(2.5) is 2 and round(3.5) is 4. That is deliberate - it avoids the upward bias you get from always rounding halves up - and it is worth knowing before you conclude something is broken.

Converting a string that might contain a decimal is a two-step: int(float("3.9")).

Truthiness is a conversion too

bool(x) follows the same rules as if x, which means empty containers, zero and None all convert to False. That makes bool occasionally useful for normalising a value, and it makes bool("False") a well-known trap: the string is non-empty, so it is True. Parsing a boolean from text needs an explicit mapping, not a conversion.

Implicit conversion, and where Python refuses

Python converts numeric types automatically when it can do so without losing information: 1 + 2.0 gives 3.0, and comparing an int with a float works as expected. It refuses to convert between strings and numbers, which is why "3" + 4 raises rather than guessing whether you meant 7 or "34".

That refusal is a feature. Languages that do guess produce a class of bug where a number arrives as a string from a form or a file and everything continues to work until the arithmetic silently becomes concatenation. In Python that fails at once, at the line responsible.

What each constructor actually accepts

The type names double as conversions, and each has its own idea of what it will take. Seeing them side by side removes most of the surprises:

print(int("  42  "), int("1_000"), int("ff", 16))
print(float("1e3"), float("  .5 "))
print(list("abc"), list({"a": 1}), tuple([1, 2]))
print(str([1, 2]), str(None))
42 1000 255
1000.0 0.5
['a', 'b', 'c'] ['a'] (1, 2)
[1, 2] None

int tolerates surrounding whitespace and the underscores Python allows in numeric literals, and takes a base as a second argument — which is how you read hexadecimal, binary and octal from text without writing a parser.

list on a dictionary gives its keys, not its items, which catches people who expected pairs. list on a string gives characters, which is occasionally what you want and more often a sign that split was meant.

str never fails. Every object has a string form, so str(x) always returns something — which is convenient and means a bug can travel a long way disguised as text. str(None) is the string "None", and a "None" written into a CSV column is a classic way to lose the distinction between missing and present.

Parsing a boolean from text

There is no conversion for this, and reaching for bool is the mistake everyone makes once:

print(bool("False"), bool("0"), bool(""))
True True False

bool on a string asks only whether the string is empty. "False" and "0" are both non-empty, so both are True, which is precisely backwards from what the text says.

Reading a boolean out of a config file, an environment variable or a form means deciding what counts, and then saying so:

TRUE = {"1", "true", "yes", "on"}

def as_bool(text):
    return str(text).strip().lower() in TRUE

That is a policy rather than a conversion, which is why the language does not provide one — different formats disagree about whether "y", "on" or "enabled" should count, and about what an unrecognised value means. Deciding explicitly is the point.

Where conversion loses information

Some conversions are exact and some throw information away, and knowing which is which prevents a family of quiet bugs.

int(3.9) discards the fractional part. int from a large float loses precision before it even starts, because the float never held the exact value. float(some_big_int) is worse: integers in Python are unbounded, floats are not, so a sufficiently large integer converts to a float that is merely nearby, and converting back does not return the original.

str of a float is lossy in the other direction historically, though modern Python prints the shortest string that round-trips exactly, so float(str(x)) == x holds.

set(items) discards duplicates and order. dict(pairs) discards all but the last value for each repeated key. Neither reports what it dropped, and both are sometimes used deliberately for exactly that effect — which is why the loss has to be intentional rather than discovered.

The habit worth forming: when a conversion narrows — float to int, list to set, anything to str — ask what happens to what does not fit. When it widens, as int to float or str to list, there is usually nothing to worry about.

The numeric types beyond int and float

int and float cover most work, and two more exist for cases where floats give the wrong answer.

decimal.Decimal represents numbers in base ten, so Decimal("0.1") * 3 is exactly Decimal("0.3") rather than a hair off. It is the right type for money, invoices, tax and anything where a result will be compared against a figure a person calculated. Build it from a string, not a float: Decimal(0.1) faithfully captures the float's error, which defeats the point.

fractions.Fraction holds an exact ratio, so Fraction(1, 3) * 3 is exactly 1. It is the right type for exact rational arithmetic, and it is slower and grows in memory as denominators multiply, so it belongs in calculations rather than in stored data.

Both convert to and from the ordinary types, and both interoperate with int — but mixing either with float in one expression converts back to float and reintroduces the error you were avoiding, which is the trap. If a calculation is meant to be exact, every value in it has to be the exact type.

complex also exists, written 3+4j, and is genuinely used in signal processing and geometry. It is worth knowing mainly so that j in a numeric literal is not a mystery.

Convert once, at the edge

The thread running through this page is that conversion is a decision, and the useful discipline is to make each decision exactly once, where the data enters the program.

A value read from a file, a form, an environment variable or an API is text. If it is converted at the point of use, every use site repeats the conversion, every use site has to handle the failure, and the sites will eventually disagree — one treats an empty string as zero, another as an error, a third crashes. The type of the value becomes something a reader has to infer from context.

Converting at the boundary means one place decides what a bad value means, and everything after that point works with a real int, date or Decimal. The error, when it comes, names the input and the field rather than surfacing as a TypeError deep in a calculation.

This is the same argument as validating nested data at the boundary, and the same argument for keyword-only parameters: push the ambiguity to one place, as early as possible, and let everything downstream rely on what it was given.

The one conversion Python does silently

Everything on this page says Python refuses to convert without being asked, which is true with one systematic exception: numeric promotion.

Mixing an int and a float in an expression converts the int to a float first, so 1 + 2.0 gives 3.0 and 1 / 2 gives 0.5 rather than 0. Mixing a bool with a number treats True as 1 and False as 0, which is why sum([True, False, True]) is 2 — occasionally useful for counting how many conditions held, and occasionally a surprise.

The rule is that Python converts within the numeric tower only, and only in the widening direction, where nothing is lost. int to float is allowed; str to anything is not. That is why True + 1 works and "1" + 1 does not, which looks inconsistent until you see the boundary it is drawing.

The one place the widening does lose something is very large integers, where converting to float loses precision even though the conversion is nominally widening. It is the exception to the exception, and it only bites at magnitudes most programs never reach.

Questions people ask

Why does int("3.9") fail when int(3.9) works? The string is not an integer literal, so it refuses rather than choosing a rounding direction. Use int(float("3.9")) when you mean truncation.

How do I convert a list of strings to numbers? [int(x) for x in items], or list(map(int, items)).

What is the difference between str and repr? str is for people, repr is for programmers and aims to be unambiguous. print uses str; the interactive prompt and containers use repr.

Why is 0.1 + 0.2 not 0.3? Binary floating point cannot represent those decimals exactly. Use math.isclose to compare, or decimal.Decimal for money.

Does int() round or truncate? Truncates toward zero, so int(-3.9) is -3. round is the one that rounds.

How do I convert between a string and bytes? text.encode("utf-8") and data.decode("utf-8"). Both need an encoding, and guessing is where mojibake comes from.

Can I convert a dictionary to a list of pairs? list(d.items()). Plain list(d) gives the keys.

Should I annotate types instead of converting? They are different jobs. An annotation documents and is checked by tools; it does not convert anything at runtime.

How do I convert a string to a date? datetime.strptime(text, fmt) with an explicit format, or datetime.fromisoformat for ISO-8601 text. Neither guesses.

What does int() with no argument give? Zero. Every numeric constructor called with nothing returns its zero value, which is occasionally useful as a default factory.

Recap in one screen

  • Each type's name is its conversion; there is nothing extra to memorise.
  • Input is always text, and Python will never convert it for you — that refusal is what makes "3" + 4 an error rather than a silent bug.
  • int from a string is strict, int from a float truncates toward zero, and round rounds halves to even.
  • bool on a string only asks whether it is empty, so parsing a boolean from text needs an explicit mapping.
  • Narrowing conversions lose information silently — duplicates, order, precision, the difference between missing and "None".

Check yourself

0 of 3

Answer without scrolling back up.

  1. What does `int("3.9")` do?

  2. `round(2.5)` returns what?

  3. `bool("0")` is:

Cheat sheet

Type Conversion

Python does not convert types behind your back. You ask, using the type name as a function, and it either succeeds or raises — it will not guess.

PYTHON · vizlearn.in/python/type_conversion.html

About the author

Ashish Jangra builds and maintains VizLearn. Every module here is written and the visualisation behind it hand-built, so the numbers in a readout come from the same code that draws the picture. Corrections are genuinely welcome and get priority over everything else — if a page states something wrong, or an animation misrepresents what the algorithm does, get in touch.