5 Widespread Python Gotchas (And How To Keep away from Them)
Picture by Creator
Python is a beginner-friendly and versatile programming language recognized for its simplicity and readability. Its elegant syntax, nonetheless, will not be proof against quirks that may shock even skilled Python builders. And understanding these is important for writing bug-free code—or pain-free debugging if you’ll.
This tutorial explores a few of these gotchas: mutable defaults, variable scope in loops and comprehensions, tuple project, and extra. We’ll code easy examples to see why issues work the way in which they do, and in addition have a look at how we will keep away from these (if we truly can 🙂).
So let’s get began!
In Python, mutable defaults are widespread sharp corners. You’ll run into sudden conduct anytime you outline a perform with mutable objects, like lists or dictionaries, as default arguments.
The default worth is evaluated solely as soon as, when the perform is outlined, and never every time the perform is known as. This may result in sudden conduct for those who mutate the default argument throughout the perform.
Let’s take an instance:
def add_to_cart(merchandise, cart=[]):
cart.append(merchandise)
return cart
On this instance, add_to_cart
is a perform that takes an merchandise and appends it to an inventory cart
. The default worth of cart
is an empty listing. That means calling the perform with out an merchandise so as to add returns an empty cart.
And listed here are a few perform calls:
# Person 1 provides gadgets to their cart
user1_cart = add_to_cart("Apple")
print("Person 1 Cart:", user1_cart)
This works as anticipated. However what occurs now?
# Person 2 provides gadgets to their cart
user2_cart = add_to_cart("Cookies")
print("Person 2 Cart:", user2_cart)
Output >>>
['Apple', 'Cookies'] # Person 2 by no means added apples to their cart!
As a result of the default argument is an inventory—a mutable object—it retains its state between perform calls. So every time you name add_to_cart
, it appends the worth to the identical listing object created through the perform definition. On this instance, it’s like all customers sharing the identical cart.
How To Keep away from
As a workaround, you’ll be able to set cart
to None
and initialize the cart contained in the perform like so:
def add_to_cart(merchandise, cart=None):
if cart is None:
cart = []
cart.append(merchandise)
return cart
So every consumer now has a separate cart. 🙂
For those who want a refresher on Python features and performance arguments, learn Python Function Arguments: A Definitive Guide.
Python’s scope oddities name for a tutorial of their very own. However we’ll have a look at one such oddity right here.
Take a look at the next snippet:
x = 10
squares = []
for x in vary(5):
squares.append(x ** 2)
print("Squares listing:", squares)
# x is accessible right here and is the final worth of the looping var
print("x after for loop:", x)
The variable x
is ready to 10. However x
can be the looping variable. However we’d assume that the looping variable’s scope is proscribed to the for loop block, sure?
Let’s have a look at the output:
Output >>>
Squares listing: [0, 1, 4, 9, 16]
x after for loop: 4
We see that x
is now 4, the ultimate worth it takes within the loop, and never the preliminary worth of 10 we set it to.
Now let’s see what occurs if we change the for loop with a comprehension expression:
x = 10
squares = [x ** 2 for x in range(5)]
print("Squares listing:", squares)
# x is 10 right here
print("x after listing comprehension:", x)
Right here, x
is 10, the worth we set it to earlier than the comprehension expression:
Output >>>
Squares listing: [0, 1, 4, 9, 16]
x after listing comprehension: 10
How To Keep away from
To keep away from sudden conduct: For those who’re utilizing loops, make sure that you don’t title the looping variable the identical as one other variable you’d wish to entry later.
In Python, we use the is
key phrase for checking object identification. That means it checks whether or not two variables reference the identical object in reminiscence. And to verify for equality, we use the ==
operator. Sure?
Now, begin a Python REPL and run the next code:
>>> a = 7
>>> b = 7
>>> a == 7
True
>>> a is b
True
Now run this:
>>> x = 280
>>> y = 280
>>> x == y
True
>>> x is y
False
Wait, why does this occur? Properly, this is because of “integer caching” or “interning” in CPython, the usual implementation of Python.
CPython caches integer objects within the vary of -5 to 256. That means each time you employ an integer inside this vary, Python will use the identical object in reminiscence. Due to this fact, while you examine two integers inside this vary utilizing the is
key phrase, the result’s True
as a result of they consult with the identical object in reminiscence.
That’s why a is b
returns True
. You too can confirm this by printing out id(a)
and id(b)
.
Nonetheless, integers outdoors this vary should not cached. And every prevalence of such integers creates a brand new object in reminiscence.
So while you examine two integers outdoors the cached vary utilizing the is
key phrase (sure, x
and y
each set to 280 in our instance), the result’s False
as a result of they’re certainly two completely different objects in reminiscence.
How To Keep away from
This conduct shouldn’t be an issue except you attempt to use the is
for evaluating equality of two objects. So at all times use the ==
operator to verify if any two Python objects have the identical worth.
For those who’re acquainted with built-in knowledge constructions in Python, you already know that tuples are immutable. So that you can’t modify them in place. Information constructions like lists and dictionaries, alternatively, are mutable. That means you can change them in place.
However what about tuples that comprise a number of mutable objects?
It is useful to began a Python REPL and run this easy instance:
>>> my_tuple = ([1,2],3,4)
>>> my_tuple[0].append(3)
>>> my_tuple
([1, 2, 3], 3, 4)
Right here, the primary aspect of the tuple is an inventory with two parts. We strive appending 3 to the primary listing and it really works high-quality! Properly, did we simply modify a tuple in place?
Now let’s attempt to add two extra parts to the listing, however this time utilizing the += operator:
>>> my_tuple[0] += [4,5]
Traceback (most up-to-date name final):
File "", line 1, in
TypeError: 'tuple' object doesn't assist merchandise project
Sure, you get a TypeError which says the tuple object doesn’t assist merchandise project. Which is predicted. However let’s verify the tuple:
>>> my_tuple
([1, 2, 3, 4, 5], 3, 4)
We see that parts 4 and 5 have been added to the listing! Did this system simply throw an error and succeed on the similar time?
Properly the += operator internally works by calling the __iadd__()
technique which performs in-place addition and modifies the listing in place. The project raises a TypeError exception, however the addition of parts to the top of the listing has already succeeded. += is probably the sharpest nook!
How To Keep away from
To keep away from such quirks in your program, strive utilizing tuples solely for immutable collections. And keep away from utilizing mutable objects as tuple parts as a lot as attainable.
Mutability has been a recurring subject in our dialogue so far. So right here’s one other one to wrap up this tutorial.
Typically it’s possible you’ll have to create unbiased copies of lists. However what occurs while you create a duplicate utilizing a syntax just like list2 = list1
the place list1
is the unique listing?
It’s a shallow copy that will get created. So it solely copies the references to the unique parts of the listing. Modifying parts by way of the shallow copy will have an effect on each the unique listing and the shallow copy.
Let’s take this instance:
original_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
# Shallow copy of the unique listing
shallow_copy = original_list
# Modify the shallow copy
shallow_copy[0][0] = 100
# Print each the lists
print("Unique Record:", original_list)
print("Shallow Copy:", shallow_copy)
We see that the modifications to the shallow copy additionally have an effect on the unique listing:
Output >>>
Unique Record: [[100, 2, 3], [4, 5, 6], [7, 8, 9]]
Shallow Copy: [[100, 2, 3], [4, 5, 6], [7, 8, 9]]
Right here, we modify the primary aspect of the primary nested listing within the shallow copy: shallow_copy[0][0] = 100
. However we see that the modification impacts each the unique listing and the shallow copy.
How To Keep away from
To keep away from this, you’ll be able to create a deep copy like so:
import copy
original_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
# Deep copy of the unique listing
deep_copy = copy.deepcopy(original_list)
# Modify a component of the deep copy
deep_copy[0][0] = 100
# Print each lists
print("Unique Record:", original_list)
print("Deep Copy:", deep_copy)
Now, any modification to the deep copy leaves the unique listing unchanged.
Output >>>
Unique Record: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
Deep Copy: [[100, 2, 3], [4, 5, 6], [7, 8, 9]]
And that’s a wrap! On this tutorial, we have explored a number of oddities in Python: from the shocking conduct of mutable defaults to the subtleties of shallow copying lists. That is solely an introduction to Python’s oddities and is on no account an exhaustive listing. You’ll find all of the code examples on GitHub.
As you retain coding for longer in Python—and perceive the language higher—you’ll maybe run into many extra of those. So, hold coding, hold exploring!
Oh, and tell us within the feedback for those who’d prefer to learn a sequel to this tutorial.
Bala Priya C is a developer and technical author from India. She likes working on the intersection of math, programming, knowledge science, and content material creation. Her areas of curiosity and experience embody DevOps, knowledge science, and pure language processing. She enjoys studying, writing, coding, and low! At the moment, she’s engaged on studying and sharing her data with the developer neighborhood by authoring tutorials, how-to guides, opinion items, and extra. Bala additionally creates partaking useful resource overviews and coding tutorials.