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.