Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Python Module 2 – Values, variables, numbers and strings

Variables, objects and names in Python

How Python variables work: names bound to objects, assignment, unpacking and swapping, type() and id(), naming rules, keywords and shadowed built-ins.

  • Beginner
  • 15 minutes
  • Examples run with Python 3.14.8 and Pyodide 314.0.7
  • By MySmartCoPilot

What you will learn

  • Explain that a variable is a name bound to an object
  • Use assignment, multiple assignment and swapping
  • Choose readable names and avoid shadowing built-ins

Before you start

On this page

A variable in Python is a name that refers to an object. The statement price = 120 does two things: Python gets an integer object with the value 120 (creating one if it needs to), and it binds the name price to that object. The name holds no number of its own; whenever you use price, Python follows the binding to the object. Keep that picture in mind and most of the surprises in this lesson stop being surprises.

Names refer to objects

Everything a program works with (a number, a piece of text, a list, even a function) is an object, and every object has three properties:

  • a type, which decides what you can do with it: type(x) returns it;
  • a value, which == compares;
  • an identity, which never changes while the object exists: x is y is True when both names refer to the very same object, and id(x) returns the identity as a number.

Assignment never copies an object. Giving a list a second name gives you one list with two names:

Two names for one list Python · two_names.py
scores = [72, 85]
marks = scores  # a second name for the same list, not a copy

marks.append(90)  # change the list through one name ...
print(scores)  # ... and the other name sees the change
print(marks is scores)  # True: one object, two names
print(id(marks) == id(scores))  # the same identity

marks = [50]  # rebinding: marks now names a brand-new list
print(scores)  # the first list is untouched
print(marks)
print(marks is scores)

Output

[72, 85, 90]
True
True
[72, 85, 90]
[50]
False

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 two_names.py

marks.append(90) changes the list itself, so the change is visible through both names. marks = [50] is a different kind of step: it builds a new list and moves the name marks to it, and scores keeps the list it had. The program compares identities (id(marks) == id(scores)) instead of printing them, because the numbers themselves change from one run to the next.

Two names, scores and marks, point to one list; after marks = [50], marks points to a new list and scores still to the old one.1. marks = scoresmarks.append(90)2. marks = [50]scoresmarkslist[72, 85, 90]scoresmarkslist[72, 85, 90]new list[50]

Names point to objects; assignment moves a name

Text description of the diagram

The diagram shows the example two_names.py in two steps, from top to bottom.

  1. After marks = scores and marks.append(90): the names scores and marks both point to the same list object, which holds 72, 85 and 90. The append went through marks, and scores sees it because there is only one list.
  2. After marks = [50]: scores still points to the list holding 72, 85 and 90, and marks now points to a new list object holding 50. Rebinding marks did not change the first list.

Two words are worth keeping apart. To mutate an object is to change it in place, as append does to a list. To rebind a name is to point it at another object, which is what = does. Numbers and strings can never be mutated, so for them only rebinding exists. Lists, dictionaries and sets can be mutated, and that is when two names for one object start to matter.

Types belong to objects, not names

A name has no type of its own. The same name may refer to an integer now and to a string later; that is what dynamic typing means. The objects do have types, and every operation checks them when it runs:

A name changes its object; an object keeps its type Python · name_and_type.py
reply = 42
print(type(reply))

reply = "forty-two"  # the same name, now bound to a str
print(type(reply))

print(type(3.5), type(True), type(None))

label = "Items: "
count = 3
print(label + str(count))  # convert on purpose, then join
print(label + count)  # Python will not convert for you

Output (exit status 1)

<class 'int'>
<class 'str'>
<class 'float'> <class 'bool'> <class 'NoneType'>
Items: 3

Printed as an error (standard error)

Traceback (most recent call last):
  File "name_and_type.py", line 12, in <module>
    print(label + count)  # Python will not convert for you
          ~~~~~~^~~~~~~
TypeError: can only concatenate str (not "int") to str

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 name_and_type.py

The last line stops with a TypeError. Joining text with + needs two strings, and + never quietly turns the number 3 into text for you. This is what people mean when they call Python strongly typed: you convert on purpose, as str(count) does on the line before. "3" + 4 fails in the same way.

Ways to assign

Four assignment forms Python · assignment_forms.py
width, height = 1920, 1080  # one name for each value
print(width, height)

left, right = "tea", "coffee"
left, right = right, left  # swap: the right-hand side is built first
print(left, right)

first, *rest = [5, 8, 13, 21]  # the starred name collects the rest
print(first, rest)

retries = errors = 0  # two names for one int: fine, ints never change
retries = retries + 1  # rebinds retries only
print(retries, errors)

Output

1920 1080
coffee tea
5 [8, 13, 21]
1 0

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 assignment_forms.py

  • width, height = 1920, 1080 assigns two names in one statement. The right-hand side is a tuple of two values, and the names on the left receive them in order; this is called unpacking. The counts must match: a, b = 1, 2, 3 stops with ValueError: too many values to unpack (expected 2, got 3).
  • left, right = right, left swaps two values with no temporary variable. Python evaluates the whole right-hand side first, a tuple holding the two old objects, and only then binds the names on the left.
  • In first, *rest = [5, 8, 13, 21], the starred name collects, as a list, whatever the other names do not take.
  • retries = errors = 0 binds both names to one object. With a number that is harmless: retries = retries + 1 creates a new integer and rebinds retries alone, so errors is still 0.

A common mistake: one list with two names

The chained form that was harmless with a number is a trap with a list:

Two names, one shopping list Python · shared_list.py
to_buy = bought = []  # looks like two empty lists, but it is one
to_buy.append("rice")
print("to buy:", to_buy)
print("bought:", bought)  # "rice" shows up here too

to_buy, bought = [], []  # two separate lists
to_buy.append("dal")
print("to buy:", to_buy)
print("bought:", bought)

Output

to buy: ['rice']
bought: ['rice']
to buy: ['dal']
bought: []

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 shared_list.py

to_buy = bought = [] creates a single empty list and gives it two names, so whatever is added to “to buy” also appears as “bought”. When you want two lists, create two: to_buy, bought = [], [], or two separate lines. Each [] always creates a new list.

Choosing names

The rules

A name, or identifier, is made of letters, digits and underscores, and it cannot start with a digit. Case matters: total, Total and TOTAL are three different names. Letters from any script count, so नाम = "Asha" is a valid assignment (Python 3 has accepted such names from its first release, PEP 3131), and there is no length limit.

The keywords are reserved and can never be names. The standard library’s keyword module lists them, together with the soft keywords, which act as keywords in one kind of statement and are ordinary names everywhere else:

Keywords, soft keywords and isidentifier() Python · keywords.py
import keyword

print(len(keyword.kwlist), "keywords:")
print(" ".join(keyword.kwlist))
print("Soft keywords:", keyword.softkwlist)

for text in ["total", "Total", "_draft", "नाम", "2fast", "first-name", "class", "match"]:
    print(repr(text), "identifier:", text.isidentifier(), "keyword:", keyword.iskeyword(text))

Output

35 keywords:
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
Soft keywords: ['_', 'case', 'match', 'type']
'total' identifier: True keyword: False
'Total' identifier: True keyword: False
'_draft' identifier: True keyword: False
'नाम' identifier: True keyword: False
'2fast' identifier: False keyword: False
'first-name' identifier: False keyword: False
'class' identifier: True keyword: True
'match' identifier: True keyword: False

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 keywords.py

Python 3.14 has 35 keywords. str.isidentifier() checks only the spelling rules, so it accepts class; keyword.iskeyword() is the second check. match passes both, because it is special only at the start of a match statement, so match = 3 is allowed, if confusing. When the name you want is a keyword, PEP 8 suggests adding an underscore at the end: class_.

Version note

Python 3.15 adds a fifth soft keyword, lazy, which marks an import as lazy (PEP 810). Programs that already use lazy as a name keep working, which is exactly why new keywords are added as soft ones. This track runs Python 3.14, whose list is shown above.

The style

The rules say what Python accepts; style is about what people can read. PEP 8, Python’s style guide, recommends:

  • Variables and functions: lower case, with underscores between words (snake_case), as in total_marks and send_report.
  • Constants: capitals, with underscores between words, as in MAX_RETRIES and TAX_RATE.
  • Classes: each word capitalised and no underscores (CapWords), as in ShoppingCart.

Python does not enforce constants: MAX_RETRIES = 5 can still be reassigned, and the capitals only ask other readers not to. PEP 8 also says never to use l, O or I as a one-letter name, because in many fonts they look like 1 and 0. Above all, choose names that say what a value means: seconds_left tells the next reader far more than s.

Names that look alike

Because names may use any script, two names can look identical and still be different:

Two names that look the same Python · lookalike_names.py
import unicodedata

pay = 100
pаy = 0  # looks the same, but this "а" is not the Latin letter
print(pay)  # still 100: the line above created a second name

print("pay" == "pаy")
print(unicodedata.name("pay"[1]))
print(unicodedata.name("pаy"[1]))

Output

100
False
LATIN SMALL LETTER A
CYRILLIC SMALL LETTER A

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 lookalike_names.py

The second pаy is spelt with the Cyrillic letter а, which looks exactly like the Latin a, so Python sees a new name, and the line that was meant to reset the pay left it alone. Python does merge some look-alikes: names are converted to the Unicode normal form NFKC as the file is read, so a typographic ligature such as fi becomes the two letters fi. Latin and Cyrillic letters are different letters, though, and stay apart. Such characters get into code copied from documents, chat messages and web pages.

Unicode Character Inspector Paste a name that misbehaves to see the code point and script of each of its characters.

Don’t hide the built-ins

The built-in functions and types, such as list, str, sum, max, id, input and type, are names too, and Python looks them up only after your own names. Assign to one of them, and your object hides the built-in for the rest of the program or shell session:

Hiding the built-in list in the shell Python console · shadowed_list.pycon
>>> list = ["pen", "ink"]
>>> list("abc")
Traceback (most recent call last):
  File "<python-input-1>", line 1, in <module>
    list("abc")
    ~~~~^^^^^^^
TypeError: 'list' object is not callable
>>> del list
>>> list("abc")
['a', 'b', 'c']

This session was replayed with Python 3.14.8 on macOS 26 arm64, and it printed exactly what is shown.

'list' object is not callable means the name list now refers to a list, and a list cannot be called the way the list type can. Pick another name, such as supplies or items. In the shell, del list removes your binding, and the built-in becomes visible again. type is easy to hide by accident, because “type” is such a common word for a setting, and being a soft keyword does not protect it: type = "admin" is allowed, and every later type(x) then fails.

Python Online Compiler Run this lesson's examples in your browser and change the names to see what breaks.

Key takeaways

  • A variable is a name bound to an object. Assignment binds a name; it never copies the object.
  • Every object has a type, a value and an identity: type() shows the first, == compares values, and is compares identities.
  • Rebinding a name (x = …) and mutating an object (items.append(…)) are different operations, and only a mutation is seen through the object’s other names.
  • a, b = b, a swaps two values; a = b = [] gives one list two names.
  • Names are case-sensitive and may use letters of any script. Keywords cannot be names, soft keywords can, and PEP 8 asks for snake_case.
  • Never use the names of built-ins such as list, str, id or type for your own values.

Exercise

Exercise · Easy · Python

Check whether a word can be a variable name

Write a function is_valid_name(text) that returns True when the string text can be used as a variable name in Python 3.14, and False when it cannot. A word can be a name when both of these hold:

  • It follows the rules for names: letters (from any script), digits and underscores, not starting with a digit. The string method isidentifier() checks exactly these rules.
  • It is not a keyword. keyword.iskeyword(text), from the standard library's keyword module, tells you whether it is one. Soft keywords such as match and type are not reserved, so they count as valid names.

Some results your function should give:

  • is_valid_name("total") is True
  • is_valid_name("नाम") is True
  • is_valid_name("match") is True
  • is_valid_name("2fast") is False
  • is_valid_name("first-name") is False
  • is_valid_name("class") is False

The sample tests import is_valid_name from names.py and try twenty words, including the empty string.

Starter code · names.py

import keyword


def is_valid_name(text):
    """Return True if text can be used as a variable name, otherwise False."""
    # Replace this line with your code.
    return False
The sample tests · test_names.py
from names import is_valid_name


def test_plain_names():
    """accepts ordinary names made of letters, digits and underscores"""
    assert is_valid_name("total") is True
    assert is_valid_name("Total") is True
    assert is_valid_name("total_2") is True
    assert is_valid_name("_draft") is True
    assert is_valid_name("MAX_SIZE") is True


def test_other_scripts():
    """accepts names written in other scripts"""
    assert is_valid_name("नाम") is True
    assert is_valid_name("café") is True


def test_bad_characters():
    """rejects names that start with a digit or contain a hyphen or a space"""
    assert is_valid_name("2fast") is False
    assert is_valid_name("first-name") is False
    assert is_valid_name("first name") is False
    assert is_valid_name("price$") is False


def test_empty_string():
    """rejects the empty string"""
    assert is_valid_name("") is False


def test_keywords():
    """rejects keywords, including True, False and None"""
    assert is_valid_name("class") is False
    assert is_valid_name("for") is False
    assert is_valid_name("True") is False
    assert is_valid_name("None") is False


def test_soft_keywords():
    """accepts soft keywords, which are ordinary names outside match and type statements"""
    assert is_valid_name("match") is True
    assert is_valid_name("case") is True
    assert is_valid_name("type") is True
    assert is_valid_name("_") is True
A hint

Try both checks on "class" first: "class".isidentifier() is True, because a keyword is spelt like any other name, but keyword.iskeyword("class") is also True. A valid name needs the first check to be True and the second to be False, so join them with and and not.

The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Check yourself

5 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 5 What does this program print?

    Read the code, then choose one answer.

    x = 10
    y = x
    x = x + 5
    print(x, y)
    Show the answer to question 1

    Answer: 15 10

    y = x binds y to the integer 10. x = x + 5 creates a new integer, 15, and rebinds x alone; integers cannot change, so the object y refers to is still 10.

  2. Question 2 of 5 a names a list. What does b = a do?

    Choose one answer.

    Show the answer to question 2

    Answer: It binds b to the same list, so both names refer to one object

    Assignment never copies. After b = a, b is a is True, and a change made through either name, such as a.append(1), is seen through the other. Rebinding one of the names later (a = []) does not move the other.

  3. Question 3 of 5 Which of these can be used as variable names in Python 3.14?

    Choose every answer that is right.

    Show the answer to question 3

    Answer:

    • नाम
    • _total
    • match

    Names are letters of any script, digits and underscores, not starting with a digit, so _total and नाम are valid. match is a soft keyword, an ordinary name outside a match statement. 2nd_place starts with a digit, first-name contains a hyphen (Python reads it as a subtraction), and class is a keyword.

  4. Question 4 of 5 What does shared_list.py print?

    What does this program print? Choose one answer.

    to_buy = bought = []  # looks like two empty lists, but it is one
    to_buy.append("rice")
    print("to buy:", to_buy)
    print("bought:", bought)  # "rice" shows up here too
    
    to_buy, bought = [], []  # two separate lists
    to_buy.append("dal")
    print("to buy:", to_buy)
    print("bought:", bought)
    Show the answer to question 4

    Answer: it prints

    to buy: ['rice']
    bought: ['rice']
    to buy: ['dal']
    bought: []

    to_buy = bought = [] creates one list with two names, so "rice" appears under both. The second assignment, to_buy, bought = [], [], creates two lists, so "dal" goes into to_buy only.

  5. Question 5 of 5 In a new shell you type list = [1, 2] and then list("ab"). What happens?

    Choose one answer.

    Show the answer to question 5

    Answer: A TypeError: 'list' object is not callable, because the name list now refers to your list

    Built-in names such as list are ordinary names that Python looks up after your own, so your assignment hides the built-in type. del list removes your binding and the built-in is visible again; better still, choose another name.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.