Embercode
✕
Variables and Data TypesLesson 4 of 16

45 min read

Type conversion in Python

Why it matters

Real programs mix kinds of values all the time. A count of items is a whole number, but the average you need is a decimal. A letter has to become a position in a table. A number typed by a user arrives as text and must become a number before any arithmetic can start. Each of these changes is a conversion, and a wrong conversion usually does not stop the program. It gives a value that looks plausible and is wrong. In interviews, many lost points come from one missing conversion: an average that prints as a whole number, a rounding that goes the wrong way for negative numbers, or a parse that fails on input you did not expect.

This lesson covers the conversions you will make every day: int(), float() and str(), the difference between truncation, floor and round, the division operators / and //, the way characters are numbers, parsing text, and the trap in comparing cached integers.

The idea: a conversion makes a new value

In Python, a conversion is a function call that returns a new value of another kind. The original value is not changed. Think of pouring water from one cup into another. You now have a new cup of water with a different shape. The first cup still holds what it held before.

Python · editable
7 7.0
<class 'int'> <class 'float'>

The function float made a new decimal value, 7.0. The name n still points to the whole number 7. Python does not change the kind of a name when you assign to it, and a name can point to a value of any kind. The conversion happens only where you call the function.

Python has no cast in parentheses, like (int) x in Java. You call int(x), float(x) or str(x) instead. Some conversions happen on their own inside arithmetic. When an int and a float meet in one expression, Python turns the int into a float for you, so 7 + 0.5 is 7.5. The result is a float because the more precise kind wins.

Mixing whole numbers and decimals

Adding an int to a float gives a float. The rule is the same for every operator, so you rarely need to think about it.

Python · editable
7.5
9.0
10.5
<class 'int'>
<class 'float'>

The conversion is safe in this direction, because every whole number can be held as a decimal without losing its value. Going the other way loses information, and that is where the next section starts.

Turning a float into an int: truncation and rounding

The decimal part of a float is dropped when you call int() on it. Python does not round. It truncates, which means it cuts towards zero. The module math has functions for the other rules, and round is built in. Read the next program and predict each line before you run it.

Python · editable
9
-9
2
-3
3
2
-2
2
<class 'int'>

Trace the first three lines. int(9.99) is 9, and int(-9.99) is -9, because truncation moves toward zero for both signs. int(2.9) is 2, not 3.

Now the other functions, which each pick a rule. math.floor rounds down, toward negative infinity, so math.floor(-2.5) is -3. math.ceil rounds up, so math.ceil(2.1) is 3. round rounds to the nearest whole number. At an exact half, round goes to the nearest even whole number, so round(2.5) is 2 and round(-2.5) is -2. This is different from the rule many people learn at school, where halves always go up. The even rule avoids a small bias when you round many values. math.floor and int disagree for negative numbers, because truncation of -2.5 gives -2 and floor gives -3. Interviewers test exactly this difference, so name the rule you want before you choose a function.

ExpressionResultRule
int(2.9)2truncate toward zero
int(-2.9)-2truncate toward zero
math.floor(-2.5)-3toward negative infinity
math.ceil(2.1)3toward positive infinity
round(2.5)2nearest, an exact half goes to the even number
round(3.5)4nearest, an exact half goes to the even number

Each of math.floor, math.ceil and round returns an int when you call it with no second argument. That is why the grade report in the variables lesson printed 78 and not 78.0.

Division: / gives a decimal, // gives a whole number

In Python 3, / always gives a float, even when both sides are int. The operator // gives a whole number and rounds down. There is no integer division trap with /, but the same question appears with //: you must choose the operator that matches the meaning.

Python · editable
3.5
3
3
-3.5
-4
-3

Read each line. a / b is 3.5, a decimal. a // b is 3, because floor division rounds down. int(a / b) is also 3, because truncation moves 3.5 toward zero. For positive numbers the two agree. For negative numbers they do not. -7 // 2 is -4, while int(-7 / 2) is -3. Use // when you want the floor, for example to split a list in half, and use int(x / y) when you want the value truncated toward zero.

Characters are numbers

Every character has a numeric code. The function ord gives the code of a one-letter string, and chr gives the character for a code. The codes for the lowercase letters are consecutive, so ord("b") is 98 and ord("z") is 122. This is the foundation for counting letters with a list, which you used in the strings lesson.

Python · editable
65
B
98
b
25

The first line turns a character into its code. The second turns the code 66 back into a character. Line 3 is the surprise: ord("a") + 1 is a number, so it prints 98, not b. Line 4 converts the number back to a character, which gives b. The last line shows the distance between two letters, which is the index used by the counting method.

Narrowing: a value that does not fit

Converting a large value to a smaller kind can lose information. In Java, a cast to byte silently wraps the value. Python int has no fixed size, so int never wraps and there is no narrowing cast for it. You meet the wrap only when you ask for a fixed size on purpose, with the ctypes module.

Python · editable
-126
-1294967296
130
0b10000010
10000010

The first line is the wrap. ctypes.c_int8 is an 8-bit signed value, and 130 does not fit in it, because the largest value is 127. The bits of 130 are 10000010. The top bit is 1, so an 8-bit signed value reads them as 130 minus 256, which is -126. The second line is the same wrap for a 32-bit value. The third line shows the mask & 0xFF, which keeps only the lowest eight bits and leaves 130 unchanged, because 130 already fits in eight bits. The last two lines show the bit pattern as text. bin gives the binary digits with a 0b prefix, and format(..., "08b") gives exactly eight digits.

So the narrowing rule is about bits. A fixed-size type keeps only the bits it has room for, and reads the top bit as the sign. Python's int keeps every bit, so the rule does not apply to it. Use ctypes or a mask only when a problem asks for a fixed size.

Text into numbers: int(), float() and str()

A conversion from text to a number is called parsing. Python gives you int() for whole numbers and float() for decimals. The reverse direction is str(), which turns any value into text.

Python · editable
43
7.0
991
100

The third line shows the trap. s is the text "99", so s + "1" joins two pieces of text and gives "991". If s were the number 99, adding 1 would give 100. The last line converts s back to a number first. Keeping track of which values are text is the most common source of this kind of mistake.

Parsing can fail. If the text is not a valid number, the function raises an error. Here are the messages for the two most common inputs.

print(int("3.5"))
Traceback (most recent call last):
  File ".../parse.py", line 1, in <module>
    print(int("3.5"))
          ~~~^^^^^^^
ValueError: invalid literal for int() with base 10: '3.5'

The message names the text that failed, which is the first thing to check. A decimal point is not allowed in int(), so parse "3.5" with float(). If a whole number is written with a decimal point, such as "42.0", int() still fails. Convert with float() first, and then with int().

Input from a user is always text. input() returns a str, so you convert it yourself:

count = int(input())
numbers = [int(input()) for _ in range(count)]
print(sum(numbers) / count)

The input() call waits for a line typed by the user, so this program is not in the Run box. The list comprehension reads count lines and converts each one. The average divides the sum by the count with /, so the result has its fraction.

Python also converts bool to a number. True counts as 1 and False counts as 0, so True + 1 is 2. This is rarely useful in code, but it explains results you may see when you add up a list of yes-or-no answers.

Identity versus equality: the cache for small integers

Python can hold two different objects that have the same value. The keyword is checks whether two names point to the same object, and == checks whether the values are equal. For most code you need ==. The difference shows up for integers, because CPython keeps one shared object for each whole number from -5 to 256, and creates a new object for other values.

Python · editable
True True
True False

The values 100 and 1000 are equal in both cases. The first pair is True for is, because both names point to the shared object for 100. The second pair is False for is, because each int("1000") made its own object. The shared object is a storage detail, so the rule is simple: use == to compare numbers. The one common use of is is to check for None, which the next section covers.

Note that int("1000") is computed at run time here. Two equal literal values in one file can sometimes share an object, so the result of is on literals is not something to rely on.

None cannot become a number

None means "no value". Python does not turn it into a number, so passing it to int() fails.

value = None
print(int(value))
Traceback (most recent call last):
  File ".../none_int.py", line 2, in <module>
    print(int(value))
          ~~~^^^^^^^
TypeError: int() argument must be a string, a bytes-like object or a real number, not 'NoneType'

The message names the type that was passed, NoneType. This error often appears when a function returns None because it could not find an answer, and the caller then tries to use the result as a number. Check for None before the conversion, with if value is None:.

Indexes must be integers

A list index must be a whole number. A float is not accepted, even when its value is whole.

letters = ["a", "b", "c"]
print(letters[1.0])
Traceback (most recent call last):
  File ".../index.py", line 2, in <module>
    print(letters[1.0])
          ~~~~~~~^^^^^
TypeError: list indices must be integers or slices, not float

The fix is to make the index an int. If the value came from a division, use //, which gives an int for int inputs. A value that comes from / is always a float, so letters[len(letters) / 2] fails, and letters[len(letters) // 2] works.

Function arguments are not checked

When you call a function, Python does not check the kind of each argument. Type hints, such as x: int, describe the kind you expect, but Python ignores them when it runs the code. A type checker such as mypy reads the hints and reports a mismatch before the program runs.

Python · editable
8
abab

The second call passes text, and Python runs it without complaint. "ab" * 2 is "abab", which is valid for text. Nothing reported the mismatch, because nothing checked it. A type checker would flag the second call. This is one reason to write type hints in code that other people will read.

Worked example: a temperature table

Converting units is an everyday use of conversion, and it is a place where the division operator is easy to get wrong. The formula from Celsius to Fahrenheit is F = C * 9 / 5 + 32. The program below computes it for the same input twice, once with // and once with /. Trace each step before you run it.

Python · editable
Wrong: 98
Right: 98.6

In the first line, celsius * 9 is 333, an int. Then 333 // 5 rounds down to 66, and adding 32 gives 98. The temperature is 98.6, so the first answer is off by 0.6 degrees. In the second line, / gives 66.6, and adding 32 gives 98.6.

The formula is correct in both lines. What differs is the operator. This is why a good habit is to check the output with a value you know. Body temperature is about 37 degrees Celsius, and the answer must be near 98.6, not 98.

Debugging walkthrough: an average that is always whole

A teammate writes a program that computes the average mark of two students. The marks add up to 15, and the expected average is 7.5. The output is 7.

total = 15
count = 2
average = total // count
print("Average:", average)

Run it and the output is Average: 7. Follow the steps. First, notice the pattern: the answer is a whole number, but the average of 15 and 2 is not whole. Second, check the operator. // rounds down, so 15 divided by 2 gives 7. Third, check the other operator. / gives 7.5. Fourth, fix the line: average = total / count. Fifth, run it again and check the output is Average: 7.5. Sixth, test an odd case, such as three students with total 10, to confirm the average is 3.333...

The lesson is that the operator decides the kind of the result. The name on the left of = does not change what the operator produces. Choose / when you want the decimal average, and // when you want the whole part.

Interview questions about conversion

These are short questions with common wrong answers. Practise saying the rule and the reason.

What does int(-3.7) return? It returns -3, because int truncates toward zero. People who say -4 are thinking of math.floor.

What is chr(ord(x) + 1) when x is "a"? It is "b". The ord gives 97, adding 1 gives 98, and chr turns 98 back into a character.

Why does int("100") is int("100") give True but int("1000") is int("1000") give False? Because CPython shares one object for whole numbers from -5 to 256. Use == to compare numbers, and reserve is for None.

Your average of a list is wrong for very large lists. What do you check? Python integers do not overflow, so the sum is exact. Check the division: sum(values) / len(values) gives the decimal average, and // would drop the fraction.

When would you use math.floor instead of int? When the value can be negative and you need the result rounded toward negative infinity, such as when mapping a negative position into a range. For positive numbers int and math.floor agree.

Common mistakes

These are the mistakes beginners make with conversions. Each one has a message or a wrong output you can recognise.

  • Losing the fraction with //. total // count drops the decimal part. Use / for the decimal average.
  • Expecting round from int. int(2.9) is 2, not 3. Use round when you want the nearest whole number.
  • Passing decimal text to int. int("3.5") gives ValueError: invalid literal for int() with base 10: '3.5'. Use float("3.5").
  • Joining text and a number. "99" + 1 gives TypeError: can only concatenate str (not "int") to str. Convert with int("99") + 1 or "99" + str(1).
  • Passing None to a conversion. int(None) gives TypeError: int() argument must be ... not 'NoneType'. Check for None first.
  • Using a float as an index. letters[1.0] gives TypeError: list indices must be integers or slices, not float.
  • Comparing numbers with is. int("1000") is int("1000") can be False even though the values are equal. Use ==.
  • Assuming a function checks argument types. double("ab") runs and returns "abab". Use type hints and a type checker.

Practice

Try each exercise before you open the solution. Predict the output on paper first.

  1. Compute the average of 7 and 8 as a float and print 7.5. Use /.
  2. Print the letter E by converting the number 69 with chr. Then print the code of "E" with ord.
  3. Predict the value of int(3.99) + int(2.5), then run it to check.
  4. Read the text "123" and print its value doubled, as an int.
  5. Print the results of round(-3.5), math.floor(-3.5) and int(-3.5). Explain why the three differ.
  6. Show that the number 200 becomes -56 when it is read as an 8-bit signed value. Use ctypes.c_int8. Explain the wrap in one sentence.
  7. Read a count and that many decimal numbers, then print their average with two decimal places using an f-string. Test with 2 and 1.5 2.5.
  8. Store int("1000") in two names and compare them with == and with is. Explain the result for each.
  9. Read two words, convert each to a number with int if it is valid, and print their sum. Think about what should happen with invalid input.
  10. Explain, without running code, why 1000000 * 1000000 prints the exact value in Python, while the same expression in Java with int values would print a wrong number.
  11. Write a program that prints the next letter after the letter given by the user, for example c gives d.
  12. A teammate says int(x) and round(x) always agree for positive numbers. Find a positive example where they differ, and explain.

Each exercise has an answer below a "Show the answer" line. Try each one first, then read the answer.

  1. The average is (7 + 8) / 2, which is 7.5.

Show the answer

print((7 + 8) / 2)
  1. The letter for 69 is E, and the code of "E" is 69.

Show the answer

print(chr(69))
print(ord("E"))
  1. int(3.99) is 3 and int(2.5) is 2, so the sum is 5.

Show the answer

print(int(3.99) + int(2.5))
  1. Convert the text with int, then multiply.

Show the answer

n = int("123")
print(n * 2)

The output is 246.

  1. round(-3.5) is -4, because -3.5 is an exact half and the even whole number is -4. math.floor(-3.5) is -4, because floor goes down. int(-3.5) is -3, because truncation moves toward zero.

Show the answer

import math
 
print(round(-3.5))
print(math.floor(-3.5))
print(int(-3.5))

The output is -4, -4 and -3. The first two agree here, and the third does not.

  1. The number 200 is 200 minus 256, which is -56 as an 8-bit signed value.

Show the answer

import ctypes
 
print(ctypes.c_int8(200).value)

The output is -56. The top bit of the eight bits is 1, so the value reads as negative.

  1. Read the count, then the numbers, then print the average with two decimal places.

Show the answer

count = int(input())
numbers = [float(x) for x in input().split()]
print(f"{sum(numbers) / count:.2f}")

For the input 2 and then 1.5 2.5, the output is 2.00.

  1. The two values of 1000 are equal, but they are two objects, so == is True and is is False.

Show the answer

x = int("1000")
y = int("1000")
print(x == y)
print(x is y)
  1. Use int inside a try block, and print a message when the text is not a whole number. Catching the error with try is not taught yet, so a simple check is enough here.

Show the answer

a = input()
b = input()
if a.isdigit() and b.isdigit():
    print(int(a) + int(b))
else:
    print("Both inputs must be whole numbers.")

The method isdigit is True for text made only of digits, so the check runs before the conversion.

  1. Python int has no fixed size, so the product is exact. A Java int holds at most about 2.1 billion, and 10 to the power 12 is larger than that, so the product wraps.

  2. Read the letter, convert it to its code, add 1, and convert back.

Show the answer

letter = input()
print(chr(ord(letter) + 1))
  1. For 2.5, int(2.5) is 2, but round(2.5) is 2 too, because the exact half goes to the even number. Use 2.7: int(2.7) is 2, and round(2.7) is 3. They differ whenever the fraction is above one half.

Show the answer

print(int(2.7))
print(round(2.7))

The output is 2 and then 3.

What you now know

  • A conversion is a function call such as int, float or str that returns a new value. The original value does not change.
  • Mixing an int and a float in arithmetic gives a float. Converting a float to an int drops the fraction, which truncates toward zero.
  • round goes to the nearest even whole number at an exact half. math.floor goes down, and math.ceil goes up.
  • / always gives a decimal, and // gives a whole number rounded down. Choose the one that matches the meaning.
  • ord and chr convert between a character and its code.
  • Text becomes a number with int or float. A bad input raises ValueError, and None raises TypeError.
  • Compare numbers with ==. The is keyword compares objects, and small integers are shared, so is can give a result that looks right by accident.
Practice this topic: coding problems for Variables and Data Types →

Stuck? Ask the tutor

Ask about anything in this lesson. Answers are short and use simple words.

Sign in to ask the tutor.

← Strings in Python, your first text type