5 Widespread Python Gotchas (And How To Keep away from Them) – KDnuggets


Picture by Writer

 

Python is a beginner-friendly and versatile programming language recognized for its simplicity and readability. Its elegant syntax, nevertheless, is just not proof against quirks that may shock even skilled Python builders. And understanding these is crucial 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 best way they do, and in addition have a look at how we are able to 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 surprising conduct anytime you outline a operate with mutable objects, like lists or dictionaries, as default arguments. 

The default worth is evaluated solely as soon as, when the operate is outlined, and never every time the operate known as. This will result in surprising conduct if you happen to mutate the default argument inside the operate.

Let’s take an instance:

def add_to_cart(merchandise, cart=[]):
    cart.append(merchandise)
    return cart

 

On this instance, add_to_cart is a operate that takes an merchandise and appends it to an inventory cart. The default worth of cart is an empty checklist. Which means calling the operate with out an merchandise so as to add returns an empty cart. 

And listed here are a few operate calls:

# Person 1 provides objects to their cart
user1_cart = add_to_cart("Apple")
print("User 1 Cart:", user1_cart)  

 

 

This works as anticipated. However what occurs now?

# Person 2 provides objects to their cart
user2_cart = add_to_cart("Cookies")
print("User 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 operate calls. So every time you name add_to_cart, it appends the worth to the identical checklist object created through the operate definition. On this instance, it’s like all customers sharing the identical cart.

 

How To Keep away from

 

As a workaround, you may set cart to None and initialize the cart contained in the operate like so:

def add_to_cart(merchandise, cart=None):
    if cart is None:
        cart = []
    cart.append(merchandise)
    return cart

 

So every person now has a separate cart. 🙂

Should you want a refresher on Python features and performance arguments, learn Python Perform Arguments: A Definitive Information.

 

 

Python’s scope oddities name for a tutorial of their very own. However we’ll have a look at one such oddity right here.

Have a look at the next snippet:

x = 10
squares = []
for x in vary(5):
    squares.append(x ** 2)

print("Squares list:", 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 about to 10. However x can also 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 checklist: [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 list:", squares)  

# x is 10 right here
print("x after list comprehension:", x)

 

Right here, x is 10, the worth we set it to earlier than the comprehension expression:

Output >>>

Squares checklist: [0, 1, 4, 9, 16]
x after checklist comprehension: 10

 

How To Keep away from

 

To keep away from surprising conduct: Should you’re utilizing loops, be certain that you don’t title the looping variable the identical as one other variable you’d need to entry later.

 

 

In Python, we use the is key phrase for checking object identification. Which means it checks whether or not two variables reference the identical object in reminiscence. And to examine 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. Which means each time you employ an integer inside this vary, Python will use the identical object in reminiscence. Due to this fact, once you examine two integers inside this vary utilizing the is key phrase, the result’s True as a result of they seek advice from the identical object in reminiscence.

That’s why a is b returns True. You may as well confirm this by printing out id(a) and id(b).

Nevertheless, integers exterior this vary are usually not cached. And every prevalence of such integers creates a brand new object in reminiscence. 

So once you examine two integers exterior 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 totally different objects in reminiscence.

 

How To Keep away from

 

This conduct shouldn’t be an issue until you attempt to use the is for evaluating equality of two objects. So all the time use the == operator to examine if any two Python objects have the identical worth.

 

 

Should you’re acquainted with built-in knowledge constructions in Python, you recognize that tuples are immutable. So that you can not modify them in place. Information constructions like lists and dictionaries, alternatively, are mutable. Which 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 straightforward instance:

>>> my_tuple = ([1,2],3,4)
>>> my_tuple[0].append(3)
>>> my_tuple
([1, 2, 3], 3, 4)

 

Right here, the primary factor of the tuple is an inventory with two components. We attempt appending 3 to the primary checklist and it really works fantastic! Properly, did we simply modify a tuple in place?

Now let’s attempt to add two extra components to the checklist, 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 help merchandise project

 

Sure, you get a TypeError which says the tuple object doesn’t help merchandise project. Which is anticipated. However let’s examine the tuple: 

>>> my_tuple
([1, 2, 3, 4, 5], 3, 4)

 

We see that components 4 and 5 have been added to the checklist! Did this system simply throw an error and succeed on the similar time?

Properly the += operator internally works by calling the __iadd__() methodology which performs in-place addition and modifies the checklist in place. The project raises a TypeError exception, however the addition of components to the tip of the checklist has already succeeded. += is maybe the sharpest nook!

 

How To Keep away from

 

To keep away from such quirks in your program, attempt utilizing tuples solely for immutable collections. And keep away from utilizing mutable objects as tuple components as a lot as attainable.

 

 

Mutability has been a recurring matter in our dialogue up to now. So right here’s one other one to wrap up this tutorial.

Generally chances are you’ll must create unbiased copies of lists. However what occurs once you create a duplicate utilizing a syntax much like list2 = list1 the place list1 is the unique checklist?

It’s a shallow copy that will get created. So it solely copies the references to the unique components of the checklist. Modifying components by means of the shallow copy will have an effect on each the unique checklist 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 checklist
shallow_copy = original_list

# Modify the shallow copy
shallow_copy[0][0] = 100

# Print each the lists
print("Original List:", original_list)
print("Shallow Copy:", shallow_copy)

 

We see that the adjustments to the shallow copy additionally have an effect on the unique checklist:

Output >>>

Unique Listing: [[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 factor of the primary nested checklist within the shallow copy: shallow_copy[0][0] = 100. However we see that the modification impacts each the unique checklist and the shallow copy. 

 

How To Keep away from

 

To keep away from this, you may create a deep copy like so:

import copy

original_list = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

# Deep copy of the unique checklist
deep_copy = copy.deepcopy(original_list)

# Modify a component of the deep copy
deep_copy[0][0] = 100

# Print each lists
print("Original List:", original_list)
print("Deep Copy:", deep_copy)

 

Now, any modification to the deep copy leaves the unique checklist unchanged.

Output >>>

Unique Listing: [[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 checklist. Yow will discover 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, maintain coding, maintain exploring!

Oh, and tell us within the feedback if you happen to’d wish 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 information with the developer neighborhood by authoring tutorials, how-to guides, opinion items, and extra. Bala additionally creates participating useful resource overviews and coding tutorials.

Recent articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here