In the last lesson, we introduced the concept of references and mentioned that everything is always passed by reference in Python. Let us experiment with a list as our first known mutable object.
But first, we need to learn about tools for our experiments: the id
function and the is
operator.
The id
and is
methods
If you refer to the function description (help(id)
), the documentation will tell you:
id(obj, /)
Return the identity of an object.
It is guaranteed to be unique among simultaneously existing objects.
The id
function returns a unique identifier of an object passed to it as an argument and by reference.
The identifier is an ordinary number. But each object has a unique identifier.
It means that any two objects will always have different identifiers. Python does not preserve identifiers from one run to the next, so the object-identifier relationship is unbreakable within one run.
That is why identifiers help keep track of object references we pass between different code sections.
The object identifier will be the same regardless of what reference we use to access the object:
a = "some string"
b = a
id(a) # 139739990935280
id(b) # 139739990935280
print(a is b) # => True
When we assign a value of one variable to another, we create a new named reference to the original value. Therefore id(a)
and id(b)
return the same result.
The is
operator checks if the identifiers of its operands are equal. In this example, both variables refer to the same object, so checking a is b
gives True
.
Checking for equality of identifiers is very quick. And it is convenient to use when we deal with so-called single objects.
The most famous Python singles are:
True
False
None
So the None
check is usually written like this:
...
if foo is None:
...
Lists, tuples, and references
Check out this example:
a = [1, 2, 3]
b = a
a.append(4)
print(b) # => [1, 2, 3, 4]
Here we see that lists a
and b
have been changed. There are not two lists but two references to one list. We will continue:
a = []
l = [a, a]
a.append(1)
print(l) # => [[1], [1]]
As you may have guessed, the list stores two references to the same object, which is also mutable. It is the subtlety of working with the references. When we get them from somewhere, we cannot be sure that the object will not change over time without our involvement.
Remember when we said that a tuple cannot change? Let us look at this example:
a = []
pair = (a, a)
pair[0].append(1)
pair[1].append(2)
print(pair) # => ([1, 2], [1, 2])
As you can see, the value in the tuple has changed. It happened because the tuple contents are references to values. These references cannot change, but the objects connected to them may well change.
Let us observe lists and tuples created using the special syntax: multiplication of a list or tuple by a number.
If we multiply a list or tuple by n
, we get a new collection of the corresponding type.
It consists of n
repetitions of elements of the original collection. Here are a few examples:
print([1, 2, 3] * 3) # => [1, 2, 3, 1, 2, 3, 1, 2, 3]
print(('foo', 'bar') * 2) # => ('foo', 'bar', 'foo', 'bar')
print([[]] * 5) # => [[], [], [], [], []]
print(((),) * 5) # => ((), (), (), (), ())
Do not forget that collections are always collections of references. You can guess how these duplicated collections will behave when the items being modified change. Take a look:
t = ([], [], []) * 3
print(t) # => ([], [], [], [], [], [], [], [], [])
t[0].append(42)
t[1].append(0)
print(t) # => ([42], [0], [], [42], [0], [], [42], [0], [])
References and assignments
We saw that you can add more than one reference to a single object to a list. And that variables are the same as references, just named.
But what happens to variables and list items when you assign them? Let us have a look:
a = "foo"
id(a) # 139739990954536
a += "bar"
print(a) # => 'foobar'
id(a) # 139739952783688
This example shows that the variable name is not strictly related to the reference to the value.
Assigning a variable (+=
is a type of assignment) can change one reference to another. This property is also characteristic of list items:
a = "foo"
l = [a, a]
print(l[0] is l[1]) # => True
l[0] += "bar"
print(l) # => ['foobar', 'foo']
print(l[0] is l[1]) # => False
Here the first two items in the list refer to the same value. However, when you assign a new value to the first element, you break the link between the original value and the element.
Are there any more questions? Ask them in the Discussion section.
The Hexlet support team or other students will answer you.
For full access to the course you need a professional subscription.
A professional subscription will give you full access to all Hexlet courses, projects and lifetime access to the theory of lessons learned. You can cancel your subscription at any time.