Currently, I am using Python and JavaScript simultaneously to automate tests for two projects using Selenium and Postman.
I am relatively new to JavaScript and had a tight timeline to learn it. So, I challenged myself to learn its “vanilla basics” in just seven days. I noticed that the features of its object type are quite similar to Python's dictionary data structure, including the assignment, shallow copy, and deep copy features which I will share in my next articles.
In this short article, we will use the is
keyword and ==
operator to understand the Pythonic concepts of assignment, shallow and deep copy.
The is
keyword checks if two variables point to the same object while ==
checks if two variables have the same value or if they are equal to the same value.
This sounds simple but can get complex in practical scenarios:
>>> a = 256
>>> b = 256
>>> a is b
True
>>> id(a), id(b) # because they point to the same memory location
(140501052082576, 140501052082576)
>>> a == b
True
In the above example, variables a
and b
point to the same memory location, 140501052082576
, in my computer RAM after I assigned a
and b
with the same value of 256. In other words, they are the same object with two different variable names.
So, both the statements a is b
and a == b
are True
as expected.
But things get a bit weird if we change the value to 257.
>>> a = 257
>>> b = 257
>>> a == b
True
>>> a is b # The twist is here
False
The twist is that a
and b
now point to two different RAM locations. They are no more the same object!
The Proof of Concept:
>>> id(a), id(b)
(140501049663216, 140501049662992)
Python Documentation says
The current implementation keeps an array of integer objects for all integers between
-5
and256
. When you create anint
in that range you actually just get back a reference to the existing object.
Now we are equipped with two important pieces of information:
is
compares if two variables refer to the same object. In other words, if both the variables are the same object in a memory location
==
compares if two variables have the same value
Let’s utilize our knowledge to understand different types of copies in Python.
First, we will declare and populate a dict_1
with some data and assign it to dict_2
. Now, dict_1
and dict_1
will point to the same object.
# Example 1
dict_1 = {
'guitar': 'Yamaha',
'strings': 7,
'pedals':{
'cry_baby': 'Electro-Harmonix',
'distortion': 'Boss',
'delay': 'TC Electronics'
},
'numbers': [1,2,3],
}
dict_2 = dict_1
print(id(dict_1), id(dict_2))
print(dict_1 is dict_2)
"""
$ python3 example.py
140283083210048 140283083210048
True
"""
Any key:value
changes in either of the variable, any type or nested, will bring change on the reference object that will be reflected in both variables.
So, both their values and reference objects remain the same.
# Example 2
dict_2['delay'] = 'Line 6'
print(id(dict_1), id(dict_2))
print(dict_1 == dict_2)
print(dict_1 is dict_2)
"""
$ python3 examples.py
140319473249600 140319473249600
True
True
"""
Python 3x
’s builtin copy
module lets us do shallow
and deep
copies. Let’s start with a shallow copy.
A shallow copy constructs a new compound object and then (to the extent possible) inserts references into it to the objects found in the original.
In other words, a shallow copy creates a new object that holds a reference to the objects found in the parent or original variable.
So, they are different objects.
# Example 3
import copy
dict_1 = {
'guitar': 'Yamaha',
'strings': 7,
'pedals':{
'cry_baby': 'Electro-Harmonix',
'distortion': 'Boss',
'delay': 'TC Electronics'
},
'numbers': [1,2,3],
}
dict_2 = copy.copy(dict_1)
print(id(dict_1), id(dict_2))
print(id(dict_1['guitar']), id(dict_2['guitar']))
print(id(dict_1['pedals']), id(dict_2['pedals']))
"""
$ python hello.py
1448723023744 1448723087936 # Different objects created
1448723023792 1448723023792 # Points to same guitar
1448723023488 1448723023488 # Points to same pedals
"""
Notice two things:
dict_1
and dict_2
have two different memory addresses. Here, dict_2
holds reference to the objects found in dict_1
dict_2
holds reference to guitar
and pedal
objects in dict_1
statement 1
If we bring any change in the immutable
or non-nested values in dict_1
that will not affect dict_2
and vice-versa.
statement 2
If we bring any change in the mutable
or nested values in dict_1
that will affect dict_2
and vice-versa.
Shallow Copy: Changes on nested or mutable data
Let’s start with statement 2
. Let’s change the distortion
value of dict_2
into Zoom
.
Take a deep breath and some time to understand what just happened:
# Example 4
dict_2['pedals']['distortion'] = "Zoom"
print(dict_1 is dict_2)
print(dict_2 == dict_1)
"""
$ python3 example.py
False
True
"""
💡 Explanation
The change made in the nested dictionary in dict_2
were brought in two different objects, dict_1
and dict_1
situated at 1448723023744 and 1448723087936
in my computer RAM. So, dict_1 is dict_2
returns False
. But both the objects have the same value and dict_2 == dict_1
the statement returns True
.
Why does this happen?
Because, when we bring any change in pedals
this refers to the same memory location in the RAM.
Have a look at the PoC
print(f"Before Change id(dict_2['pedals']): {id(dict_2['pedals'])}\
id(dict_1['pedals']): {id(dict_1['pedals'])}")
dict_2['pedals']['distortion'] = "Zoom"
print(f"After Change id(dict_2['pedals']): {id(dict_2['pedals'])}\
id(dict_1['pedals']): {id(dict_1['pedals'])}")
"""
$ python3 example.py
Before Change id(dict_2['pedals']): 140285477350656 id(dict_1['pedals']): 140285477350656
After Change id(dict_2['pedals']): 140285477350656 id(dict_1['pedals']): 140285477350656
"""
Task
Modify the numbers
in dict_1
and see what happens to dict_2
Shallow Copy: Changes on immutable data
Let’s get back to statement 1
that says “If we bring any change in the immutable
or non-nested values in dict_1
that will not affect dict_2
and vice-versa.”
Bring a change in the guitar
key’s value in dict_1
and see the effect
dict_1['guitar'] = 'Ibanez'
print(dict_1 is dict_2)
print(dict_2 == dict_1)
"""
$ python3 example.py
False
False
"""
💡 Explanation
The change brought in dict_1
's guitar
value takes place in two different objects as well. But, dict_1
's guitar
now points to a different memory location that left dict_2
's guitar
untouched.
So, neither dict_1
and dict_2
are same objects nor they have same values.
Have a look at the PoC
print("Before assignment")
print(dict_1 is dict_2)
print(id(dict_1['guitar']), id(dict_2['guitar']) )
print(dict_1['guitar'] is dict_2['guitar'])
dict_1['guitar'] = 'Ibanez'
print("After assignment")
print(dict_1 is dict_2)
print(id(dict_1['guitar']), id(dict_2['guitar']) )
print(dict_1['guitar'] is dict_2['guitar'])
"""
$ python3 example.py
Before assignment
False
139863551636464 139863551636464
True
After assignment
False
139863550623600 139863551636464
False
"""
If a reader came here reading the whole text along with executing the examples, this segment needs only a few words and examples
Python Documentation says
A deep copy constructs a new compound object and then, recursively, inserts copies into it of the objects found in the original.
In other words, a deep copy creates a new object that recursively creates a separate copy of the objects from the parent / original variable. A deep copy ensures that any changes in the original or assigned variable will not affect each other.
import copy
dict_1 = {
'guitar': 'Yamaha',
'strings': 7,
'pedals':{
'cry_baby': 'Electro-Harmonix',
'distortion': 'Boss',
'delay': 'TC Electronics'
},
'numbers': [1,2,3],
}
dict_2 = copy.deepcopy(dict_1)
print(id(dict_2), id(dict_1)) # ...1
print(id(dict_2['strings']), id(dict_1['strings'])) # ...2
print(id(dict_2['pedals']), id(dict_1['pedals'])) # ...3
"""
$ python3 Shallow.py
140331302249472 140331302661568
140331303287280 140331303287280
140331300866368 140331302661376
"""
💡 Explanation
Notice that a deepcopy()
of dict_1
dict_2
strings
key will point to a new object/memory location just like a shallow copydict_2
mutable and nested data.
This ensures that any changes in dict_1
or dict_2
will affect each other.
Almost every available write-up on deep and shallow copy in Python use nested list examples. I found this data type leaves a noob to intermediate programmer a bit confused, at least they need some extra effort to get the concept.
While learning copying JS objects, I found dictionary data structure would help them to visualize the Pythonic concept of deep and shallow copy concepts a bit more clear.