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.
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 yisTruewhen both names refer to the very same object, andid(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:
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
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
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.
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.
- 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.
- 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:
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
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
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
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
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
width, height = 1920, 1080assigns 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, 3stops withValueError: too many values to unpack (expected 2, got 3).left, right = right, leftswaps 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 = 0binds both names to one object. With a number that is harmless:retries = retries + 1creates a new integer and rebindsretriesalone, soerrorsis 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:
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
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
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:
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
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
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_marksandsend_report. - Constants: capitals, with underscores between words, as in
MAX_RETRIESandTAX_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:
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
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
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.
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:
>>> 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.
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, andiscompares 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, aswaps 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,idortypefor 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'skeywordmodule, tells you whether it is one. Soft keywords such asmatchandtypeare not reserved, so they count as valid names.
Some results your function should give:
is_valid_name("total")isTrueis_valid_name("नाम")isTrueis_valid_name("match")isTrueis_valid_name("2fast")isFalseis_valid_name("first-name")isFalseis_valid_name("class")isFalse
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.
Results of the sample tests
| Test | Result | Details |
|---|
What your code printed
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.
References
- Data model: objects, values and types (Python Software Foundation)
- Execution model: naming and binding (Python Software Foundation)
- Assignment statements (Python Software Foundation)
- Lexical analysis: names (identifiers and keywords) (Python Software Foundation)
- keyword, testing for Python keywords (Python Software Foundation)
- PEP 8: Style Guide for Python Code (Python Software Foundation)
- PEP 3131: Supporting Non-ASCII Identifiers (Python Software Foundation)
- PEP 810: Explicit lazy imports (Python Software Foundation)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress