Thinking in Python: Key Concepts and Pitfalls

Open In Colab

Thinking in Python: Key Concepts and Pitfalls#

This notebook contains some key concepts and pitfalls in Python programming. This notebook will be updated throughout the course.

1. Comparison by Name vs. by Value in Python#

In Python, variables store references to objects rather than the objects themselves. This means that when you assign one variable to another, both variables point to the same object in memory.

The is operator checks whether two variables reference the same object (i.e., have the same memory address). In contrast, the == operator checks whether two variables have the same content, even if they are different objects in memory. Understanding this distinction is crucial when working with mutable objects like lists.

When we assign y = x, both y and x point to the same object. As a result, modifying x also affects y. However, z = x.copy() creates a new list with the same contents as x, so changes to x do not impact z. The following example illustrates this concept:

# Illustrates how assignment and copying affect identity and equality
x = [1, 2, 3]
y = x  # y is a reference to x
z = x.copy()  # z is a shallow copy of x
x[0] = 100  # Modify x

print("x is", x)
print("y is", y) # y is a reference to x, so it will be modified
print("z is", z) # z is a copy of x, so it will not be modified

print("\nThe memory address of x is", id(x))
print("The memory address of y is", id(y))
print("The memory address of z is", id(z))
print()
print("y == x:", y == x)  # True, since y and x have the same contents
print("z == x:", z == x)  # False, since z was copied before x was modified

print("y is x:", y is x)  # True, y and x refer to the same object
print("z is x:", z is x)  # False, z is a different object

print("y == x:", y == x)
print("z == x:", z == x)
x is [100, 2, 3]
y is [100, 2, 3]
z is [1, 2, 3]

The memory address of x is 140207141980096
The memory address of y is 140207141980096
The memory address of z is 140207141983040

y == x: True
z == x: False
y is x: True
z is x: False
y == x: True
z == x: False

When a list is passed into a function, it is passed by reference, meaning that the function operates directly on the original list. As a result, any modifications made to the list inside the function persist outside of it. In the example below, the function f(arr) modifies the original list x, so the changes are reflected in both w (the return value of f(x)) and x itself.

x = [1, 2, 3]
y = x  # y is a reference to x
z = x.copy()  # z is a copy of x at this point
y[0] = 11  # Modify y, which also affects x
print("x:", x)
print("y:", x)
print("z:", z)

def f(arr):
    arr[0] = 100  # Modify the passed array
    return arr

w = f(x)
print(f"w = {w}")  # w is a modified copy of x
print("y == x:", y == x)  # True, because y and x still share the same memory
print("x is", x)
print("y is", y)
print("z is", z)
print("z == x:", z == x)  # True, since z was copied before any modifications
x: [11, 2, 3]
y: [11, 2, 3]
z: [1, 2, 3]
w = [100, 2, 3]
y == x: True
x is [100, 2, 3]
y is [100, 2, 3]
z is [1, 2, 3]
z == x: False

2. Defining a Tuple with One Element#

To define a tuple with one element, you must include a comma after the element. Otherwise, Python will interpret the parentheses as defining an expression rather than a tuple.

For example, x = (1) assigns the integer 1 to x, while x = (1,) assigns a tuple containing the integer 1 to x. The following example illustrates this concept:

x = (1)
y = (1,)

print("x is", x)
print("y is", y)
print("x == y:", x == y)
print(y[0]) # prints 1
print(x[0]) # produces an error
x is 1
y is (1,)
x == y: False
1
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[3], line 8
      6 print("x == y:", x == y)
      7 print(y[0]) # prints 1
----> 8 print(x[0]) # produces an error

TypeError: 'int' object is not subscriptable