AdvancedPythonHacks
AdvancedPythonHacks
https://powerfulpython.com
1. Class Methods
Use @classmmethod to create methods on classes:
class Money:
def __init__(self, dollars, cents):
self.dollars = dollars
self.cents = cents
@classmethod
def from_pennies(cls, pennies):
dollars = pennies // 100
cents = pennies % 100
return cls(dollars, cents)
Benefit: Write handy "alternate constructors" to instantiate your class from special data
representations, or handle extra one-time configuration. And it automatically extends to subclasses!
# NO!!!
try:
# ...
except:
pass
1
Writing "except: pass" creates horrible bugs that are almost impossible to figure out.
Instead, always make your except block catch the most specific exception available that will do
what you need. And NOT catch anything else.
Look carefully at this code - can you spot the hidden bug:
try:
print(fruitinfo["carbohydrates"])
except KeyError:
print("carbohydrate info not available")
The dictionary is fruit_info, but inside the try block, we forget the underscore. This creates a
NameError, which is NOT caught by the except block. That’s great! We can see the bug right away and
fix it immediately.
If we wrote "except: pass", we might not notice this bug until we already shipped the code!
def make_squares(limit):
squares = []
for num in range(limit):
squares.append(num**2)
return squares
def gen_squares(limit):
for num in range(limit):
yield num**2
Benefit: This is more memory efficient and potentially more responsive, as it gives you one
element at a time to process, instead of insisting on creating ALL of them in a list all at once.
2
>>> # int() can take a number or a string, but not a list
>>> int([1, 2, 3])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: int() argument must be a string, a bytes-like object or a number, not
'list'
ValueError means you passed in an acceptable type, but this particular value is not acceptable:
Benefit: More quickly understand the error messages you see, and how to quickly fix them.
Bonus: When raising exceptions in your own functions and methods, using an exception the way
Python does makes your code easier to work with and understand.
5. List Comprehensions
Instead of this:
>>> squares = []
>>> for n in range(5):
... squares.append(n**2)
...
>>> print(squares)
[0, 1, 4, 9, 16]
Write this:
Benefit: More readable and expressive data structures, in a way that is easy to understand even for
developers who have never seen list comprehensions before.
6. Key Functions
Pass a "key" function to sorted(), max() and min(), to alter their default comparison mode:
3
>>> # A list of strings, that we want to sort by their numerical value.
>>> nums = ["7", "12", "9", "3"]
>>>
>>> # By itself, sorted() does not sort them like we want.
>>> sorted(nums)
['12', '3', '7', '9']
Benefit: Clearer and shorter code that uses Python’s built-in tools in powerful and useful ways.
7. Unit Tests
Write automated tests to verify your code stays correct:
def to_url(https://www.scribd.com/phrase):
return "/" + phrase.lower().replace(" ", "-") + ".html"
class TestForFunction(TestCase):
def test_to_url(https://www.scribd.com/self):
self.assertEqual(
"/how-to-level-up-your-python.html",
to_url(https://www.scribd.com/"How%20To%20Level%20Up%20Your%20Python"))
Benefit: Higher quality, more robust code. And makes it possible for you to create powerfully
complex software that was just too overwhelming before.
8. F-strings
F-strings let you create strings from local variables:
4
>>> name = "John"
>>> dollars = 3
>>> cents = 5
>>> food = "DONUTS"
>>>
>>> f"{name} gave me ${dollars}.{cents:02} for {food.lower()}"
'John gave me $3.05 for donuts'
Benefit: Render strings intuitively and concisely. Also clearly demonstrates you are up-to-date on
Python, as f-strings are a relatively recent addition to the language.
9. Properties
Create methods that act like member variables with @property:
class Money:
def __init__(self, dollars, cents):
self.dollars = dollars
self.cents = cents
@property
def total_cents(self):
return 100 * self.dollars + self.cents
(Notice there is no "(" after cash.total_cents. It’s acting like a member variable, even though you
defined it as a method.)
Benefit: Dynamically calculated values, data validation, denormalizing, and can sometimes also
help in refactoring.
5
job = MachineLearningJob(job_spec)
cloud_vm = VirtualMachine()
Even if run_job() raises a program-crashing exception, the stop() method still gets called.
11. Lambdas
Imagine you need to find the highest-protein food in this data set:
You can of course define a key function, and use it with max():
Benefit: No need to define a function you will only use once. Different readability trade-offs, which
- when used wisely - creates more readably concise code.
6
formats:
timestamps = [
"02/05/30 11:19pm EST",
"6/8/31 22:35",
"Mon, Sep 16, 2019, 11:52 AM",
"6/24/2031 7:13:44 AM",
"5/15/2030",
"2031-05-12 13:45:17",
]
Solution: install the dateutil module, and use its parse() function:
Benefit: Easily handles any date and time format you will ever encounter - automatically, easily
and accurately.
7
def read_text_files(*paths):
text = ""
for path in paths:
with open(path) as text_file:
text += text_file.read()
return text
Benefit: Flexible function and method interfaces that allow you to write more powerfully generic
code.
class Money:
def __init__(self, dollars, cents):
self.dollars = dollars
self.cents = cents
@classmethod
def from_pennies(cls, pennies):
dollars = pennies // 100
cents = pennies % 100
return cls(dollars, cents)
@dataclass
class Money:
dollars: int
cents: int
@classmethod
def from_pennies(cls, pennies):
dollars = pennies // 100
cents = pennies % 100
return cls(dollars, cents)
8
Both versions work exactly the same:
Benefit: Save time and reduce error with repetitive __init__ definitions, plus you are documenting
the intended types.
# Lets you call the parse() function under the name parse_datetime()
from dateutil.parser import parse as parse_datetime
Benefit: More readable and intuitive names. In this example, the name parse() is confusingly
ambiguous. Parse what? JSON? XML? Python source code? But parse_datetime() is clear and takes
no mental energy to remember what it is for.
import logging
Note that even in modern Python, you must use "percent formatting" with logging, and not f-strings
or str.format().
Benefit: Useful information about what the program is doing at runtime, even in production - for
telemetry and monitoring, and also to quickly troubleshoot otherwise horrendous bugs.
9
17. Use Magic Methods
See this class:
class DateRange:
def __init__(self, start, end):
self.start = start
self.end = end
assert self.start <= self.end
>>> dr.in_range(vday)
True
>>> dr.in_range(eve)
False
in_range() is handy, but it would be nice if we could use Python’s built-in "in" operator:
class DateRange:
# ...
Now it works:
10
>>> vday in dr
True
>>> eve in dr
False
Python has many "magic methods" like this, all starting and ending with two underscores. Each lets
you "hook" into Python’s syntax in some fun, useful way like this.
• Learn the high-level Python skills that earn you a higher salary,
• Not to mention the joy of creating more powerful and interesting software…
https://powerfulpython.com
11