Python patterns only beginners use

There are a few things that you won’t ever see senior Python developers committing to code, but may not be explicitly taught.
Most of these things you won’t even see senior programmers doing in their small side scripts. However you should never see or write these patterns in production code.

Catching too much

Exceptions should only be around the most concise affected code. Often when starting out with python, people believe they should just catch anything that could possibly error. However, that is counter productive.

If something is going to fail unexpectedly, you want it to be loud and and happen as close to where the problem is as possible. That way you can diagnose it a lot easier.

Here’s something I have seen many a time, the “gotta catch ’em all!” code block.

# BAD CODE

from urllib.request import Request, urlopen
import json

try:
    req = Request(
        url='http://example.com/something.json',
        method='GET')
    res = urlopen(req, timeout=5)
    data = json.load(res)
except Exception as err:
    print(f"Something bad happened: {err}")

There are two ways it is catching too much:

  • How many lines of code it catches
  • Doesn’t specify a more exact type of exception it knows how to handle

Here’s a better way to approach it.

# GOOD CODE

from urllib.request import Request, urlopen
from urllib.error import HTTPError
import json


request = Request(
    url='http://example.com/something.json', 
    method='GET')

try:
    result = urlopen(request, timeout=5)
except HTTPError as err:
    print(f"Could not reach {request.full_url} due to an error: {err}") 
else:
    data = json.load(result)

Check out Exception! Exception! Read all about it! for a more in depth look on how to handle exceptions properly.

Passing around executable code

If you find yourself pickling stuff to store, or using ast.literal or ever call eval or exec on code stored as a string, you’re doing it wrong. They add security risks, lose interoperability, and are generally a lot slower.

Those are tools that might be useful if you’re in a terminal or Jupyter Notebook, but shouldn’t be used in production code.

If you want to store stuff for latter or pass it through a message queue or API, it needs to be in a proper serialized format. Such as JSON, MessagePack or Protocol Buffers.

# BAD CODE
import pickle 

class Starship:
    def __init__(self, ship_type):
        self.ship_type = ship_type

my_starship = Starship("fighter") 

saved_ship = pickle.dumps(my_starship)

...

my_starship = pickle.loads(saved_ship)

Pickle does make things easy is why it’s tempting. But look at what saved_ship would equal:

b'\x80\x04\x956\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x08Starship\x94\x93\x94)\x81\x94}\x94\x8c\tship_type\x94\x8c\x07fighter\x94sb.' 


It’s totally unreadable, and then has to be unpickled in a dangerous matter. Even the docs have the large warning:

It is possible to construct malicious pickle data which will execute arbitrary code during unpickling. Never unpickle data that could have come from an untrusted source, or that could have been tampered with.

https://docs.python.org/3/library/pickle.html

Instead, you need to spend the time to add proper save and load methods yourself that can serialize the data you care about.

# GOOD CODE
import json

class Starship:
    def __init__(self, ship_type):
        self.ship_type = ship_type

    def dumps(self):
        return json.dumps({"Starship": self.ship_type})

    @classmethod
    def loads(cls, serialized_starship):
        return cls(ship_type=json.loads(serialized_starship)["Starship"])


my_starship = Starship("fighter")

saved_ship = my_starship.dumps()

...

my_starship = Starship.loads(saved_ship)

Now you have your serialized data as simply '{"Starship": "fighter"}' and a much safer and faster way of saving and loading your data, that could also be loaded by different programing languages if needed.

Not using a full IDE and debugger

Notepad++ and VIM are powerful text editors, but they aren’t good enough. No, adding 200 extensions to VIM still isn’t a replacement. If you want to actually catch errors and debug bad code, use something like PyCharm or VSCode.

Everything from unreachable code to spelling mistakes can be easily skimmed over in a pull request, but will be easily caught with an actual IDE.

PyCharm has a full on “Problems” tab that will show all the warnings and errors in your code, and helps you fix them. Here is a quick show of a project that other developers were using tools “just as good as an IDE!” like VIM, and leaving me this mess to clean up.

Non-reusable patterns

Keep your code DRY – Don’t Repeat Yourself

“Smart” solutions

“Hey check out this cool one liner I spent a few hours on to optimize!” said the coder about to get their code demolished in a code review.

nested_list = [[2,1,2,3],[1,2,3,4],[4,4,[16,1,3]]]
[[[print(y) if type(y) is not list else print(x) for x in y] if type(y) == list else print(y) for y in z]for z in nested_list]

Look at that mess. It’s totally valid, but is very hard to read. You might not even have noticed that it was improperly using type(y) == list instead of an isinstance check.

Just because you can use a more advanced technique doesn’t mean you should. Remember, readability counts.

One time use variables

Let’s not be hasty and say that all one time use variables are bad. Sometimes the code would look messy or hard to follow without them. However the majority of the time, the code is better off without them.

Reinventing the wheel

There is a lot of power in the built-ins provided by Python that plenty of developers have never used. The ones I find myself returning too are usually in collections, itertools, and functools.

I have seen dozens of implementations of cycle, partial, Counter and lru_cache by themselves.

That being said, no one expects you to know the entire standard library. But if you find a simple pattern that solves a problem easily that seems repeatable, do a quick search to see if something similar already exists that may be more efficient and reusable.

Overwriting names of built-in functions

I know a lot of good variable names are taken by default, like type, id, sum and len, but it’s even worse to overwrite them. You don’t want to look like an total fool by having to go back to the builtins to use a base function.

# BAD CODE
print = "My string to show on stream"
id = 1
hash = "browns"

print(print)
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# TypeError: 'str' object is not callable

# if you find yourself importing from builtins, 
# you probably did something silly
import builtins
builtins.print(print)
# My string to show on stream

Instead, pick another word, or add an underscore _ after the variable name, which is standard practice.

# Better code
print_ = "My string to show on stream"
id_ = 1
my_hash = "browns!"

Naming conventions from another language

Similar to the previous one, learn the basic Naming conventions (and everything else in PEP8). This is especially important before working with others on a Python code base.

In Java you can TalkLikeAnnoyedSpongeBob, in javascript you can pull a Nicolas Cage and yell halfwayThrough, and in Python you stay cool and calm and keep your functions snake_cased.

Swearing and cussing

I have seen this enough I need to add it here.

It’s all fun and games until a traceback slips through to a client that sees some poor word choices, and then suddenly HR is asking for a git blame of who put that it.