Embercode
✕
Variables and Data TypesLesson 2 of 16

45 min read

Variables and basic types in Python

Why it matters

A program remembers things: a score, a price, a counter, the position of a search. A variable is the name that lets the program remember a value and use it later. Choosing the wrong kind of value gives a wrong answer without any error message. Your program runs, prints a number, and the number is simply false. In a coding interview this is a common way a correct idea fails. The algorithm is right, but a division gives a decimal when you wanted a whole number, a float comparison says two equal values differ, or a name is reused by accident and hides a built-in function.

This lesson teaches the basic kinds of values in Python, how to create and change names, and the traps that matter most for data structures and algorithms: the difference between / and //, the limits of floating-point numbers, and the binary search midpoint.

The idea: a name is a label on a value

Many beginners picture a variable as a box that holds a value. In Python a better picture is a sticky note. The name is the note, and the value is the object the note is stuck to. The value knows its own kind. The name does not.

age = 20

Read this line as: make the value 20 and stick the name age on it. The = sign does not mean "is equal to". It means "make this name point to the value on the right". When you write age = 21 later, the note moves to a new value, and the old 20 is left alone. If nothing points to a value any more, Python removes it.

Python is dynamically typed. The value has a type, such as int or str, and the name can be moved to a value of any type at any time. The next program shows this.

Python · editable
10 <class 'int'>
ten <class 'str'>

The name score first pointed to a whole number and then to text. Python allowed both. The function type tells you which kind of value a name points to. This flexibility is convenient, but it moves the type check from the start of the program to the moment a line runs. If you pass text where the code needs a number, the error appears on that line, and only if that line runs.

If you know Java, this is the main difference: a Java variable has a type fixed by its declaration, while a Python name has no type of its own.

Assigning and changing

A name can be given a value on one line and changed on later lines. The right side of = is always worked out first, using the current value, and only then is the name moved.

Python · editable
First score: 10
After bonus: 15
Reset: 3

Trace score = score + 5 carefully, because it looks wrong at first. The right side is evaluated first using the current value, which is 10, so the result is 15. Only then is the name score moved to the value 15. The old value 10 is no longer used. A name points to exactly one value at a time.

Python also allows several names in one line. x = y = 0 makes both names point to the value 0. Use it when the names really should start with the same value.

The basic kinds of values

Python has a small set of built-in kinds for single values. The table lists the ones this course uses most.

TypeHoldsExampleNotes
inta whole numbern = 42No fixed size. It grows as needed.
floata decimal numberd = 3.1464 bits, the same as a Java double. About 15 to 17 significant digits.
strtextname = "Asha"A single character is a str of length 1.
boolTrue or Falseok = TrueWritten with capital letters.
NoneTypeNoneresult = NoneThe value that means "no value here".

Python has no byte, short, long or char types. You do not choose a size for a whole number. An int grows to hold any value that fits in memory. A character is just a one-letter string. If you need a fixed size, for example when you store millions of small numbers, you use a special module such as array. That case is covered in the arrays module.

The function type shows the kind of any value:

Python · editable
<class 'int'>
<class 'float'>
<class 'bool'>
<class 'str'>
<class 'NoneType'>

The word class in the output is Python's name for a kind of value. It does not mean what it means in Java. You will learn classes in the module about object-oriented programming.

A bool is a kind of int underneath. True counts as 1 and False counts as 0, which is why True + True is 2. This is rarely useful, but it is worth knowing when you read other people's code.

Literals: how values look in code

A literal is a value written directly in the code, such as 42 or "Hi". Python reads a literal according to its shape, and the shape decides its type.

A whole-number literal such as 42 is an int. Underscores are allowed in numbers to make them easier to read, so 8_000_000_000 is the same as 8000000000. A number with a decimal point, such as 3.14, is a float. Text in single or double quotes is a str.

You can also write numbers in other bases. 0xFF is hexadecimal (base 16) for 255, 0b1010 is binary (base 2) for 10, and 0o17 is octal (base 8) for 15. You will see hexadecimal in colour codes and in memory addresses, and binary in bit tricks that appear in some interview questions.

Python · editable
8000000000
0.75
255
10
65
10

The function ord gives the code number of a one-letter string, and chr does the reverse. ord("A") is 65 and chr(65) is "A". The escape "\n" is a single character too, the new-line character, whose code is 10. A value that holds several characters, such as "Hi", is also a str, and the strings lesson explains it in full.

No overflow for whole numbers

In languages such as Java, an int has a fixed size. When a result is too big for it, the value silently wraps around to a negative number. Python does not do this for int. The next program adds two numbers that would overflow a 32-bit Java int.

Python · editable
sum: 3000000000
type: <class 'int'>

The sum is 3 billion, and Python keeps the exact value. The type is still int. This means you do not need a special long type for big sums in Python. The only limit is the memory of your computer.

Fixed-size numbers still exist, for the cases where you need them. The ctypes module gives a 32-bit integer that wraps around the same way a Java int does, and the array module refuses values that do not fit.

Python · editable
-1294967296
OverflowError: signed integer is greater than maximum

The first line shows the wrap: 3 billion does not fit in 32 bits, so it becomes -1294967296. The second line shows the other behaviour of array. It stops with an error instead of wrapping. The i in array.array("i", ...) means a C int, which is 32 bits on most machines. Use these only when a program needs a fixed size. Plain int is safe for sums, counts and products.

The midpoint of a binary search is affected too. Section "Binary search and the midpoint" explains the formula that matters in languages with fixed-size integers, and why it is less important in Python.

Names must exist before they are read

Python does not give a name a default value. A name exists only after a line creates it. If a line reads a name that has never been created, the program stops with an error at that line.

print(z)
Traceback (most recent call last):
  File ".../e1.py", line 1, in <module>
    print(z)
          ^
NameError: name 'z' is not defined

The message names the missing name. The fix is to give z a value before the line that reads it, for example z = 0. Python checks this when the line runs, not when the file is read. If the line that creates z is inside an if that is false, the error appears later in the run.

Attributes of an object, which you will meet in the object-oriented module, are also created when they are assigned. The rule is the same: create the name first, then read it.

Constants are a convention

Python has no final keyword that stops a name from changing. By convention, a name in capital letters with underscores between words means "this value is fixed". Python does not enforce the convention, so reassigning the name works without any error.

Python · editable
MAX_PLAYERS is 5

The program runs and prints 5. The capital letters are a message to the reader, and a type checker such as mypy warns about a second assignment only if the name is marked with typing.Final. Python itself still runs the code.

Constants make code easier to change. If the board grows from 8 to 10 squares, you change one line instead of hunting for the number 8 in twenty places. Interviewers like to see named constants, because magic numbers scattered through code are a sign of careless work.

Naming rules

A name starts with a letter or an underscore. It contains letters, digits and underscores, with no spaces and no dollar signs. Names are case-sensitive, so Total and total are different names. A name cannot be a keyword, which is a word that Python uses for its own grammar. The full list is in the keyword module:

Python · editable
['False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield']

Words such as int, str, list and print are not keywords. They are names of built-in functions and types, and Python lets you reuse them. That is a trap, because after int = 7 the built-in int is hidden in that file. The mistakes section shows the error that results.

The style guide for Python, called PEP 8, asks for snake_case for variables and functions, PascalCase for classes, and UPPER_SNAKE_CASE for constants. So the names are total_marks, left_index and MAX_MARKS. i is fine for a loop counter, but left_index tells you what the number means when you read the code six months later.

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

The operators +, - and * work on numbers as you expect. The division operator needs care. In Python 3, / always gives a float, even when both sides are whole numbers. To get a whole number, use //, which is floor division. Floor division rounds down towards negative infinity. The operator % gives the remainder.

Python · editable
3.4
3
2
-3.4
-4
3
3.4

Read the output slowly. 17 / 5 is 3.4, a decimal. 17 // 5 is 3, because 5 goes into 17 three times. 17 % 5 is 2, the remainder. For negative numbers the result is different from what many people expect. -17 // 5 is -4, not -3, because floor division rounds down, and -3.4 rounded down is -4. -17 % 5 is 3, because the remainder takes the sign of the divisor, which is positive here. The rule a == (a // b) * b + a % b holds for all values, and that is why Python's remainder is always between 0 and the divisor minus one when the divisor is positive.

If you know Java, this is different. Java's / on two integers cuts towards zero, and its % takes the sign of the left side. Python's // and % always round down. You only need to be careful when the numbers are negative.

The mistake that catches most beginners is using / where a whole number is needed. A range, a list index and a count all need a whole number. range(n / 2) fails because n / 2 is a float. Use n // 2.

Floats are not exact

A float stores decimal numbers in binary, and many decimals cannot be written exactly in binary. The number 0.1 is an endless repeating pattern in binary, just as 1/3 is an endless repeating pattern in decimal. The stored value is the closest binary number, and that closest value is a little off.

Python · editable
0.30000000000000004
False
True
0.30000000000000004
inf

The first line is the surprise. 0.1 + 0.2 is not exactly 0.3 in binary, so the stored sum is slightly larger. The == test then fails. The fix in a comparison is math.isclose, which checks that two values are close enough. It uses a relative tolerance by default, so you can compare numbers of different size. The last line shows float("inf"), the infinity value, which you can create on purpose.

Dividing a float by zero is an error in Python, not Infinity as in Java:

print(1.0 / 0)
Traceback (most recent call last):
  File ".../e4.py", line 1, in <module>
    print(1.0 / 0)
          ~~~~^~~
ZeroDivisionError: division by zero

The rule for interviews is short. Use int whenever the problem involves counting, indexes or money in whole units. Use float for measurements where a tiny error is acceptable, and never compare two floats with == unless you know both were computed the same way. For exact decimal money, the decimal module works with decimal digits and avoids the binary error. Storing money as whole paise or cents in an int is also common.

Copies of numbers are independent

When you write b = a, Python makes the name b point to the same value that a points to. For numbers, this looks like a copy, because the next change to b moves only b.

Python · editable
5 9

Trace it. Line 1 makes a point to the value 5. Line 2 makes b point to the same value 5. Line 3 moves b to a new value, 9. The name a still points to 5. Numbers in Python cannot be changed in place, so a change always moves the name. This changes with lists and other mutable values, where two names can point to one object that changes in place. The references lesson shows that case, and the same rule applies when you pass a number into a function.

Worked example: a grade report

The next program puts several types to work together. A student has three marks out of 100. You need the total, the average as a decimal, the average rounded to a whole number, and a flag that says whether the student passed. Each value gets the kind that matches its meaning.

Python · editable
Total: 233 out of 300
Average: 77.66666666666667
Rounded: 78
Passed: True
Floor division: 77
Average to 2 places: 77.67

Trace the important lines. The marks are int, because they are whole numbers. Their sum total is also int. The average needs a fraction, so we use /, which always gives a float. The line that uses total // 3 shows the other choice: floor division gives 77, which is a whole number but not the average. Choose the operator that matches the meaning.

round with no second argument returns an int, which is why rounded prints as 78 and not 78.0. round also rounds halves to the nearest even number. round(2.5) is 2 and round(3.5) is 4, so the rule is not "always round up at .5". The casting lesson returns to the difference between rounding and truncating.

passed is a bool, and the comparison average >= 40 produces True or False, which you store directly. You do not need an if to store a yes or no answer.

The f-string f"{average:.2f}" formats the number with two digits after the decimal point.

Binary search and the midpoint

Binary search is the algorithm that finds a value in a sorted list by checking the middle, then throwing away the half that cannot hold the answer. Almost every implementation computes the middle index as (low + high) // 2. In Python this formula is correct for all valid indexes, because int does not overflow.

The formula is still worth knowing. In Java and C, where int has a fixed size, the sum low + high can exceed the largest value and wrap to a negative number before the division. The safe formula low + (high - low) // 2 avoids that. Interviewers sometimes ask about it, and a Python answer should say that Python does not need it.

Here is a complete binary search. It prints the low index, the high index and the middle on each step, so you can see the search narrow down.

Python · editable
low=0 high=9 mid=4 value=16
low=5 high=9 mid=7 value=56
low=5 high=6 mid=5 value=23
found at 5
low=0 high=9 mid=4 value=16
low=5 high=9 mid=7 value=56
low=5 high=6 mid=5 value=23
low=6 high=6 mid=6 value=38
found at -1

The first search for 23 takes three steps. The middle index 4 holds 16, which is smaller than 23, so the search moves low to 5. The middle index 7 holds 56, which is too big, so high moves to 6. The middle index 5 holds 23, and the function returns 5.

The second search for 24 shows the stopping rule. At the last step, low and high are both 6, and the value 38 is too big, so high becomes 5. Now low is larger than high, the loop ends, and the function returns -1 to say that the value is not in the list. The stopping rule is the same for every search, whether the list has 10 values or a billion.

Choosing a type on purpose

You can choose a type for a problem by asking three questions. Is the value whole or fractional? Is it a yes or no answer, or a piece of text? Does the value need to be exact?

A count of students is an int. A running total of a million sales is also an int, and Python will not overflow. A measurement such as a height is a float, because a tiny error is acceptable. Money in real systems is usually stored as whole paise or cents in an int, or with the decimal module, because a binary float cannot represent every price exactly.

A question to ask in every interview is whether the input can be as large as the constraints allow. If the statement says that n can be 200000 and each value up to one billion, the sum can reach 2 times 10 to the power 14. Python can hold that sum. A Java int cannot. Writing the limit down still matters, because the same algorithm may be ported to a language with fixed-size integers, and because a huge list of numbers uses a lot of memory in Python.

ProblemTypical valuePython typeReason
Index into a list0 to a few millionintList indexes are whole numbers
Count of itemsup to millionsintNo overflow to worry about
Sum of many valuescan be hugeintExact at any size
Letter in a stringone characterstr of length 1ord gives its code
Yes or no flagtwo statesboolOnly True and False
Measurement with fractionsapproximatefloatTiny rounding errors are acceptable
Money in whole unitsexactintStore paise or cents
Exact decimal moneyexactdecimal.DecimalDecimal digits, no binary error

The table is not a rule to memorise. It shows how to ask the right question. For each name, write down what kind of value it holds, then pick the kind that matches. If you cannot say what the value looks like, the problem statement is missing a detail, and you should ask about it before you code.

Common mistakes

These are the errors beginners make with names and values. Each one has a message you will see.

  • Using ++ to add one. Python has no ++ operator. x++ gives SyntaxError: invalid syntax. Write x += 1.
  • Using = in a condition. if x = 5: gives SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='?. Use == to compare.
  • Reading a name before it exists. print(z) gives NameError: name 'z' is not defined. Create the name first.
  • Expecting a constant to stop a change. MAX_PLAYERS = 5 after MAX_PLAYERS = 4 runs without an error. Only a type checker warns about it.
  • Reusing a built-in name. After list = [1, 2], the line list(range(3)) gives TypeError: 'list' object is not callable. Choose names such as values or items.
  • Using / where a whole number is needed. range(6 / 2) gives TypeError: 'float' object cannot be interpreted as an integer. Use 6 // 2.
  • Dividing by zero. 1 / 0 and 1.0 / 0 both give ZeroDivisionError: division by zero. Check the divisor before you divide.
  • Adding text and a number. "Total: " + 233 gives TypeError: can only concatenate str (not "int") to str. Convert the number with str(233) or use an f-string.
  • Comparing floats with ==. 0.1 + 0.2 == 0.3 is False. Use math.isclose.

The mistakes that do not give an error are the most dangerous. Integer division with /, float comparison and a constant that changes all run without a message. You find them by thinking about the kinds of values, which is why this lesson spends time on them.

Debugging walkthrough: a count that is not a whole number

A teammate writes a program to split a list into two halves and loop over the first half. The program stops with an error. Here is the core of their code:

values = [4, 8, 15, 16, 23, 42]
half = len(values) / 2
for i in range(half):
    print(values[i])

Run it, and the traceback ends with TypeError: 'float' object cannot be interpreted as an integer. Follow the steps. First, read the last line, which names the kind of value that is wrong. Second, find the line that names the value: range(half). Third, check the kind of half. len(values) / 2 uses /, so half is 3.0, a float. Fourth, range needs a whole number. Fifth, fix the operator: half = len(values) // 2 gives 3, and the loop prints the first three values, 4, 8 and 15.

Notice that the fix was one character. Read the error for the kind of value it expects, then check the line that produced the value. This is the same discipline as checking an algorithm: find where the wrong kind of value was made, and fix that one step.

Practice

Try each exercise yourself before you open the solution. Write the expected output on paper first, then run your code in the Run box.

  1. Store your age, your height in metres as a float, and your first initial as a one-letter str. Print all three on separate lines with labels.
  2. Print type of each of these values: 7, 7.0, "7", True and None. Predict each line before you run it.
  3. Set DAYS_IN_WEEK = 7, then set it to 8 on the next line. Run the file and write down what Python prints. Explain why Python does not stop.
  4. Print -7 // 2 and -7 % 2, then 7 // -2 and 7 % -2. Explain each output in one sentence.
  5. Two int values are 1.2 billion each. Print their sum. Then use ctypes.c_int32 to print the same sum as a 32-bit value, and explain why the two answers differ.
  6. Print the hexadecimal value 0x1F and the binary value 0b1111 as decimal numbers.
  7. Write a program that prints the midpoint of low = 1500000000 and high = 2000000000 using (low + high) // 2. Check that the result is between the two values.
  8. Store the character "z", print its code with ord, and then print the letter that follows it using chr.
  9. Print 0.1 + 0.2 == 0.3, then print the same comparison using math.isclose.
  10. Explain, without running code, why x = 2147483647 followed by x = x + 2 prints 2147483649 in Python.
  11. Write a small program that computes the average of 7 and 8 and prints 7.5. Use /, not //.
  12. Write a program that uses round on 2.5, 3.5 and 4.5, and explain the output.

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

  1. Three values with labels, for example print("Age:", age).

Show the answer

age = 20
height = 1.72
initial = "A"
print("Age:", age)
print("Height:", height)
print("Initial:", initial)
  1. Predict before you run.

Show the answer

print(type(7))
print(type(7.0))
print(type("7"))
print(type(True))
print(type(None))

The output is <class 'int'>, <class 'float'>, <class 'str'>, <class 'bool'> and <class 'NoneType'>. The quotes around "7" make it text, so its type is str.

  1. Python does not stop, because the file has no rule about constants.

Show the answer

DAYS_IN_WEEK = 7
DAYS_IN_WEEK = 8
print(DAYS_IN_WEEK)

The output is 8. The capital letters tell readers the value should not change, but the second assignment runs. A type checker such as mypy reports it when the name is marked with typing.Final.

  1. -7 // 2 is -4, because floor division rounds down. -7 % 2 is 1, because the remainder has the sign of the divisor, which is positive here. 7 // -2 is -4 for the same reason. 7 % -2 is -1, because the remainder takes the sign of the divisor, which is negative here.

Show the answer

print(-7 // 2, -7 % 2)
print(7 // -2, 7 % -2)

The output is -4 1 and then -4 -1.

  1. The sum is 2400000000 with int. The ctypes version is -1894967296, because a 32-bit value cannot hold 2.4 billion and wraps around.

Show the answer

import ctypes
 
a = 1_200_000_000
b = 1_200_000_000
print(a + b)
print(ctypes.c_int32(a + b).value)

The output is 2400000000 and then -1894967296. The second value is what a 32-bit Java int would show.

  1. 0x1F is 31 and 0b1111 is 15.

Show the answer

print(0x1F)
print(0b1111)
  1. The midpoint of 1500000000 and 2000000000 is 1750000000.

Show the answer

low = 1_500_000_000
high = 2_000_000_000
mid = (low + high) // 2
print(mid)
print(low <= mid <= high)

The output is 1750000000 and then True.

  1. The code of "z" is 122, and the next letter is chr(123), which is {.

Show the answer

letter = "z"
print(ord(letter))
print(chr(ord(letter) + 1))

The output is 122 and then {. The second value is the next code after z in the table, not a letter of the alphabet, so the line shows why the answer is code-based.

  1. The first comparison is False. The second is True.

Show the answer

import math
 
print(0.1 + 0.2 == 0.3)
print(math.isclose(0.1 + 0.2, 0.3))
  1. Python's int has no fixed size, so the sum is exact. Two more than 2147483647 is 2147483649, and Python keeps that value. A 32-bit Java int would wrap to a negative number instead.

  2. print((7 + 8) / 2) prints 7.5. Using // would print 7.

Show the answer

print((7 + 8) / 2)
  1. The output is 2 4 4. round sends halves to the nearest even whole number, so 2.5 becomes 2, 3.5 becomes 4, and 4.5 becomes 4.

Show the answer

print(round(2.5))
print(round(3.5))
print(round(4.5))

What you now know

  • A Python name is a label on a value. Assignment moves the label. The value has the type, not the name.
  • The basic kinds are int, float, str, bool and None. Use type to check the kind of a value.
  • Python int values do not overflow. Fixed-size types exist in the ctypes and array modules when you need them.
  • / always gives a float. // gives a whole number, rounded down. % gives the remainder with the sign of the divisor.
  • Floats store decimal numbers in binary, so compare them with math.isclose, and use decimal or whole units for exact money.
  • A name must be created before it is read. Constants in capital letters are a convention that Python does not enforce.
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.

← Your first Python program