Tips and Tricks

Generate secure, URL safe, random tokens in Python


Python tip:

Use secrets.token_urlsafe() to generate security tokens.

For example, you can generate a token for a password reset👇

import secrets

url = f"https://my-app.com/password-reset/token={secrets.token_urlsafe()}"

print(url)
# => https://my-app.com/password-reset/token=KzmPWanja-z0r2BpyC7mFAZd5LXvp8SnIUbSnQyEMWw

Executing modules with runpy in Python


Python tip:

You can use runpy to run a module without importing it first. It works the same as the -m command line option.

An example👇

######## example.py ########
print("I am an example")

######## run_example.py ########
import runpy

runpy.run_module(mod_name="example")  # to run module example.py

# -> I am an example

FIFO and LIFO queue examples in Python


Python tip:

You can create a simple FIFO or LIFO queue with the queue module.

An example👇

import queue

my_queue = queue.Queue()

my_queue.put("Jan")
my_queue.put("Mike")

print(my_queue.get())
# => Jan
print(my_queue.get())
# => Mike

my_lifo_queue = queue.LifoQueue()

my_lifo_queue.put("Jan")
my_lifo_queue.put("Mike")

print(my_lifo_queue.get())
# => Mike
print(my_lifo_queue.get())
# => Jan

Using Django's choices field option


Django tip:

Use choices for character fields with a finite number of possible values.

For example, you can use it for blog's status:

from django.db import models


class Blog(models.Model):
    DRAFT = "DRF"
    PUBLISHED = "PUB"
    DELETED = "DEL"

    STATUS_CHOICES = [
        (DRAFT, "Draft"),
        (PUBLISHED, "Published"),
        (DELETED, "Deleted"),
    ]
    title = models.CharField(max_length=120)
    content = models.TextField()
    contributors = models.TextField()
    status = models.CharField(max_length=3, choices=STATUS_CHOICES, default="DRAFT")

Django CharField vs TextField


Django tip:

Use models.TextField for storing long texts inside your models instead of models.CharField since CharField has a max length of 255 characters while TextField can hold more than 255 characters.

An example:

from django.db import models


class Blog(models.Model):
    title = models.CharField(max_length=120)
    content = models.TextField()
    contributors = models.TextField()

Django Rest Framework - read only views


Django REST Framework tip:

You can use ReadOnlyModeViewSet when you don't want to allow any unsafe operations (POST, DELETE, PATCH).

An example👇

class BlogViewSet(ReadOnlyModeViewSet):
    """
    A simple ViewSet for viewing blogs.
    """

    queryset = Blog.objects.all()
    serializer_class = BlogSerializer

Python pprint – pretty-print data structures


Python tip:

You can use pprint.pprint to print a formatted representation of an object.

For example:

import json
import pprint
from urllib.request import urlopen

with urlopen("https://pypi.org/pypi/flask/json") as resp:
    project_info = json.load(resp)["info"]

pprint.pprint(project_info)

"""
{'author': 'Armin Ronacher',
 'author_email': '[email protected]',
 'bugtrack_url': None,

 ...

 'requires_python': '>=3.6',
 'summary': 'A simple framework for building complex web applications.',
 'version': '2.0.1',
 'yanked': False,
 'yanked_reason': None}
"""

Python pathlib.Path.expanduser()


Python tip:

You can use Path.expanduser from pathlib to convert ~ and ~user paths to the full path.

Example:

import pathlib

path = pathlib.Path("~/Documents/test.txt")

print(path)
# => ~/Documents/test.txt

print(path.expanduser())
# => /Users/michael/Documents/test.txt

Check if file is a symlink in Python


Python tip:

You can use pathlib.Path.is_symlink to check whether the path is a symbolic link.

An example👇

import pathlib

path = pathlib.Path("/usr/bin/python")

print(path.is_symlink())
# => True

Filename pattern matching in Python with pathlib.Path.glob()


Python tip:

You can use pathlib.Path.glob() to list all files matching a given pattern.

For example, you can list names of all txt files located inside the same directory as your Python script👇

import pathlib

parent_folder = pathlib.Path(__file__).parent.absolute()

print(parent_folder)
for file in parent_folder.glob("*.txt"):
    print(file.name)

"""
example.txt
first.txt
second.txt
"""

Sort a list of objects based on an attribute of the objects in Python


Python tip:

You can use attrgetter to sort a list of objects based on the value of the selected attribute.

An Example👇

from operator import attrgetter


class User:
    def __init__(self, username):
        self.username = username

    def __repr__(self):
        return f'User(username="{self.username}")'


users = [User("johndoe"), User("bobby"), User("marry")]

print(sorted(users, key=attrgetter("username")))
# => [User(username="bobby"), User(username="johndoe"), User(username="marry")]

Test-diven devopment and pair programming


TDD Tip:

When practicing Test-driven Development with pair programming, try having one developer write the failing test while the other writes the code to get it to pass. The first developer is the responsible for any refactoring.

Why?

  1. It's fun.
  2. This can accelerate the learning of a less experienced developer when paired with a more experienced developer.

For more TDD benefits, check out What is Test-Driven Development? .

Find the mime type of a file in Python


Python tip:

You can use mimetypes.guess_type to get a mime type and encoding for a file from its name.

For example👇

import mimetypes

mime_type, _ = mimetypes.guess_type("my _ file. pdf")
print(mime_type)
# => application/pdf

mime_type, encoding = mimetypes.guess_type("archive. tar.gz")
print(mime_type, encoding)
# => application/x-tar gzip

Python - logging with filters


Python tip:

You can use Filter from the logging module to add additional information to your log records.

For example, you can add the username of the system user who's running the program👇

import logging
import os


class UserRunningProgramFilter(logging.Filter):
    def filter(self, record):
        record.user = os.getenv("User")
        return True


if __name__ == "__main__":
    levels = (
        logging.DEBUG,
        logging.INFO,
        logging.WARNING,
        logging.ERROR,
        logging.CRITICAL,
    )
    logging.basicConfig(
        level=logging.DEBUG,
        format="%(asctime)-15s %(name)-5s %(levelname)-8s Run by User: %(user)-20s %(message)s",
    )
    logger = logging.getLogger("mylogger")

    f = UserRunningProgramFilter()
    logger.addFilter(f)
    logger.debug("A debug message")

# => 2021-09-22 07:56:45,473 mylogger DEBUG    Run by User: johndoe                 A debug message

Python For Else


Python tip:

You can use else with for loops when searching for something. Code inside the else will be executed if the break statement isn't hit.

items = ["right_one", "my_item", " another_item"]


def right_one(item_to_check):
    if item_to_check == "right_one":
        return True
    else:
        return False


for item in items:
    if right_one(item):
        print("Do something with the item")
        break
    else:
        print("I will do something else, no item to process")


"""
Do something with the item
"""


items = ["different_item", "my_item", "another_item"]


for item in items:
    if right_one(item):
        print("Do something with the item")
        break
    else:
        print("I will do something else, no item to process")

"""
I will do something else, no item to process
I will do something else, no item to process
I will do something else, no item to process
"""

Django - custom user model


Django Tip:

Use a custom user model when starting a project

When starting a new project, set up a custom user model. This model behaves just like the default user model, but you'll be able to customize it in the future.

That should be done before creating or running any migrations.

https://testdriven.io/blog/django-custom-user-model/

# users/models.py

from django.contrib.auth.models import AbstractUser


class CustomUser(AbstractUser):
    pass


# users/adming.py

from django.contrib import admin
from django.contrib.auth.admin import UserAdmin

from .models import CustomUser

admin.site.register(CustomUser, UserAdmin)


# settings.py

AUTH_USER_MODEL = "users.CustomUser"

Python - prettyprint JSON with json.dumps


Python tip:

You can use the indent parameter with json.dumps to get an indented multiline string instead of a single line.

It's much easier to read.

An example👇

import json

users = [
    {"username": "johndoe", "active": True},
    {"username": "Mary", "active": False}
]


print(json.dumps(users))
# [{"username": "johndoe", "active": true}, {"username": "Mary", "active": false}]


print(json.dumps(users, indent=2))
"""
[
  {
    "username": "johndoe",
    "active": true
  },
  {
    "username": "Mary",
    "active": false
  }
]
"""

linecache.getline() in Python


Python tip:

You can get any line from a file by using the getline method from linecache:

linecache.getline(filename, lineno, module_globals=None)

It never raises an exception. Instead, it returns '' (no line with a number, no file, ...).

The trackback module uses it to retrieve source lines for formatted tracebacks.

An example👇

import linecache

"""
Content of example. txt:

Example
Tweet
too
"""


print(linecache.getline("example.txt", 2))
# => Tweet

Check if a string is a valid keyword in Python


Python tip:

You can check whether some string is a reserved Python keyword with the keyword module.

An example👇

import keyword

""""
False       await       else        import      pass
None        break       except      in          raise
True        class       finally     is          return
and         continue    for         lambda      try
as          def         from        nonlocal    while
assert      del         global      not         with
async       elif        if          or          yield
"""

print(keyword.iskeyword("raise"))
# => True

print(keyword.iskeyword("foo"))
# => False

Comparing files in Python


Python tip:

You can check if two files are equal with cmp from the filecmp module. It returns True if the files are equal and False if they're not.

For example:

import filecmp

print(filecmp.cmp("file.pdf", "file.pdf"))
# => True

print(filecmp.cmp("file.pdf", "file1.pdf"))
# => False

Automatically setting an enum member's value in Python


Python tip:

When the exact value of an enum member is not important, you can use auto() to auto-generate it:

The value starts at 1 and then increases incrementally by 1.

https://docs.python.org/3/library/enum.html#using-automatic-values

An example👇

from enum import Enum, auto


class Status(Enum):
    DRAFT = auto()
    IN_REVIEW = auto()
    PUBLISHED = auto()
    DELETED = auto()


print(list(Status))
"""
[<Status.DRAFT: 1>, <Status.IN_REVIEW: 2>, <Status.PUBLISHED: 3>, <Status.DELETED: 4>]
"""

Python deep copy via copy.deepcopy()


You can use copy.deepcopy() to create a copy of a compound object.

deepcopy creates a copy of the object and of all its nested objects. Changes to the original object won't affect the copied object since new objects are created instead of just referencing.

import copy

house = {
    "width": 12,
    "length": 8,
    "height": 3.5,
    "doors": [
        {"type": " ENTRANCE", "width": 0.9, "height": 2.2},
        {"type": "BACK DOOR", "width": 0.7, "height": 2.0},
    ],
}

same_house = copy.deepcopy(house)
print(house)
print(same_house)
print(house == same_house)

"""
{'width': 12, 'length': 8, 'height': 3.5, 'doors': [{'type': ' ENTRANCE', 'width': 0.9, 'height': 2.2}, {'type': 'BACK DOOR', 'width': 0.7, 'height': 2.0}]}
{'width': 12, 'length': 8, 'height': 3.5, 'doors': [{'type': ' ENTRANCE', 'width': 0.9, 'height': 2.2}, {'type': 'BACK DOOR', 'width': 0.7, 'height': 2.0}]}
True
"""

house["height"] = 4.0
print(house)
print(same_house)
print(house == same_house)

"""
{'width': 12, 'length': 8, 'height': 4.0, 'doors': [{'type': ' ENTRANCE', 'width': 0.9, 'height': 2.2}, {'type': 'BACK DOOR', 'width': 0.7, 'height': 2.0}]}
{'width': 12, 'length': 8, 'height': 3.5, 'doors': [{'type': ' ENTRANCE', 'width': 0.9, 'height': 2.2}, {'type': 'BACK DOOR', 'width': 0.7, 'height': 2.0}]}
False
"""

Create a CSV file from a list of dictionaries in Python


Python tip:

You can use DictWriter to create a CSV file from a list of dictionaries.

An example👇

import csv

users = [
    {"username": "johndoe", "name": "John Doe"},
    {"username": "bob", "name": "Bob Builder"},
    {"username": "daisy", "name": "Daisy Flower"},
]

fieldnames = ["username", "name"]

with open("users.csv", "w") as csvfile:
    writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
    writer.writeheader()
    writer.writerows(users)

"""
users.csv

username,name
johndoe,John Doe
bob,Bob Builder
daisy,Daisy Flower
"""

Python - comparing two text files with difflib.HtmlDiff()


Python tip:

You can use HtmlDiff to generate an HTML table that shows a side by side, line by line comparison of the text with inter-line and intra-line change highlights.

https://docs.python.org/3/library/difflib.html#difflib.HtmlDiff

An example👇

import difflib
from pathlib import Path

first_file_lines = Path('first.txt').read_text().splitlines()
second_file_lines = Path('second.txt').read_text().splitlines()

html_diff = difflib.HtmlDiff().make_file(first_file_lines, second_file_lines)
Path('diff.html').write_text(html_diff)

Python - find all similar strings from a provided word within a list of strings


Python tip:

You can use get_close_matches from difflib to find all similar strings from a provided word within a list of strings:

difflib.get_close_matches(word, possibilities, n=3, cutoff=0.6)

For example, find similar names👇

from difflib import get_close_matches

name = "John"
names = ["John", "Jane", "Bob", "Daisy", "Jim", "Mark", "Johnny", "Mike"]


print(get_close_matches(name, names))
# => ['John', 'Johnny']

Python - Using SequenceMatcher.ratio() to find similarity between two strings


Python tip:

You can use difflib.SequenceMatcher.ratio() to get the distance between two strings:

  • T - total number of elements in both strings (len(first_string) + len(second_string))
  • M - number of matches

Distance = 2.0 * M / T -> between 0.0 and 1.0 (1.0 if the sequences are identical, and 0.0 if they don't have anyhing in common)

https://docs.python.org/3/library/difflib.html#sequencematcher-objects

For example:

from difflib import SequenceMatcher

first = "Jane"
second = "John"

print(SequenceMatcher(a=first, b=second).ratio())
# => 0.5

Decimal vs float in Python


Python tip:

You can use Decimal instead of float to:

  1. incorporate a notion of significant places (1.20 + 1.30 = 2.50)
  2. represent decimal numbers exactly (1.1 + 2.2 = 3.3)

In short, use Decimal when precision matters.

An example 👇

from decimal import Decimal

print(1.20 + 1.30)
# => 2.5

print(Decimal("1.20") + Decimal("1.30"))
# => 2.50

print(1.1 + 2.2)
# => 3.3000000000000003

print(Decimal("1.1") + Decimal("2.2"))
# => 3.3

RGB to HSV in Python with colorsys


Python tip:

You can use colorsys to convert colors between different color systems.

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

RGB to HSV👇

import colorsys

# we need values between 0 and 1, you need to divide by 255
rgb = (0.00, 0.19, 0.56)  # 00308F | (0, 48, 143) -> AirForce Blue

print(colorsys.rgb_to_hsv(*rgb))
# => (0.6101190476190476, 1.0, 0.56)

Python - __post_init__ method in Dataclasses


Python tip:

You can use __post_init__ on classes decorated with dataclass to add custom logic on init.

For example, to set a value of an attribute based on a different attribute's value:

from dataclasses import dataclass, field


@dataclass
class Order:
    net: float
    vat: float
    total: float = field(init=False)

    def __post_init__(self):
        self.total = self.net + self.vat


order = Order(net=100.00, vat=22.00)
print(order)  # => Order(net=100.0, vat=22.0, total=122.0)

Semi-immutable Python objects with frozen dataclasses


Python tip:

You can freeze instances of a class decorated with dataclass by setting frozen to True.

https://docs.python.org/3/library/dataclasses.html#frozen-instances

An error will be raised if you try to assign a new value to any of attributes.

For example:

from dataclasses import dataclass


@dataclass(frozen=True)
class Post:
    title: str
    content: str


post = Post(title="Happy new year", content="2020 is finally over")

post.title = "Awesome new year"

"""
Traceback (most recent call last):
  File "example.py", line 12, in <module>
    post.title = "Awesome new year"
  File "<string>", line 4, in __setattr__
dataclasses.FrozenInstanceError: cannot assign to field 'title'
"""

Python's monthrange() method


Python tip:

You can use monthrange from the calendar module to get the weekday of the first day in a month and the number of days in a month.

An example👇

import calendar

weekday, number_of_days = calendar.monthrange(2021, 2)

print(calendar.day_name[weekday], number_of_days)

# Monday 28

Python weekday() method from the calendar module


Python tip:

Use weekday from the calendar module to get a day in a week (0 is Monday) for a specific date.

An example👇

import calendar

days = {
    0: "Monday",
    1: "Tuesday",
    2: "Wednesday",
    3: "Thursday",
    4: "Friday",
    5: "Saturday",
    6: "Saturday",
}

day_in_week = calendar.weekday(1975, 10, 23)
print(days[day_in_week])
# Thursday

Test interactive Python examples with doctest


Python tip:

You can use the doctest module to run interactive code examples inside your docstrings.

It notifies you If any of the examples don't return the expected value.

👇

def sum_ab(a, b):
    """
    Sum numbers a and b
    >>> sum_ab(1, 3)
    4
    >>> sum_ab(-41, 50)
    1
    :return: sum of 2 numbers
    """
    return a + b


# python -m doctest example.py
# **********************************************************************
# File "example.py", line 4, in example.sum_ab
# Failed example:
#     sum_ab(-41, 50)
# Expected:
#     1
#     :return: sum of 2 numbers
# Got:
#     9
# **********************************************************************
# 1 items had failures:
#    1 of   2 in example.sum_ab
# ***Test Failed*** 1 failures.

Python itertools.zip_longest() Example


Python tip:

You can use zip_longest from itertools to zip multiple iterables of different lengths.

"Missing" values from shorter iterables are replaced by fillvalue.

An example 👇

from itertools import zip_longest

students = ["Bob", "Ann", "John", "Marry", "Daisy", "Amy"]
grades = ["A", "A+", "D"]

for student, grade in zip_longest(students, grades, fillvalue="-"):
    print(student, grade)

How do I use itertools.groupby()?


Python tip:

You can use groupby from itertools to group elements.

  1. Returns - an iterator of consecutive keys and groups from the iterable.
  2. Keys - values returned from key function
  3. Groups - iterator returns values for group

For example, to group tax IDs 👇

import re
from itertools import groupby

tax_ids = [
    "DE123456789",
    "ATU99999999",
    "BG999999999",
    "IT12345678900",
    "DE987654321",
]

grouped_tax_ids = groupby(
    tax_ids, key=lambda tax_id: re.match(r"[A-Z]{2}", tax_id).group()
)

for country, country_tax_ids in grouped_tax_ids:
    print(country, list(country_tax_ids))

# DE ['DE123456789']
# AT ['ATU99999999']
# BG ['BG999999999']
# IT ['IT12345678900']
# DE ['DE987654321']

Python's Itertools.cycle()


Python tip:

Do you need to repeat a sequence over and over again?

Use cycle from itertools 👇

from itertools import cycle

chord_sequence = cycle(["G", "D", "e", "C"])

song_chords = [next(chord_sequence) for _ in range(16)]

print(song_chords)
# ['G', 'D', 'e', 'C', 'G', 'D', 'e', 'C', 'G', 'D', 'e', 'C', 'G', 'D', 'e', 'C']

Django - Use foreign key values directly


Django tip:

Use foreign key values directly.

Make sure to use the foreign key property instead of the foreign object ID.

  • DON'T: Song.objects.get(id=1).singer.id
  • DO: Song.objects.get(id=1).singer_id
from django.db import connection, reset_queries

from .models import Song

reset_queries()

# makes an extra SQL query to load the singer object:
Song.objects.get(id=1).singer.id
len(connection.queries) # => 2

reset_queries()

# an extra SQL query is not required:
Song.objects.get(id=1).singer_id
len(connection.queries) # => 1

Python - itertools.count()


Python tip:

You can use count from itertools to make an iterator that returns evenly spaced values:

itertools.count(start=0, step=1)

For example, to generate all odd numbers greater or equal to 101👇

from itertools import count

odd_numbers = count(101, 2)

# first ten elements
for _ in range(10):
    print(next(odd_numbers))

"""
101
103
105
107
109
111
113
115
117
119
"""

Python - itertools.combinations()


Python tip:

You can generate all combinations of elements from an iterable of length n by using combinations from itertools.

For example, you can generate all possible match pairs 👇

from itertools import combinations

players = ["John", "Daisy", "Marry", "Bob"]

print(list(combinations(players, 2)))

"""
[
    ('John', 'Daisy'),
    ('John', 'Marry'),
    ('John', 'Bob'),
    ('Daisy', 'Marry'),
    ('Daisy', 'Bob'),
    ('Marry', 'Bob'),
]
"""

Function overloading with singledispatchmethod from functools


Python tip (>=3.8):

You can use singledispatchmethod to implement different (re)actions based on the first (non-self, non-cls) function's argument type.

For each type, you register an overloaded implementation.

For example, you can handle different events 👇

from functools import singledispatchmethod


class Event:
    pass


class UserSignedUp(Event):
    def __init__(self, email):
        self.email = email


class CustomerCanceledSubscription(Event):
    def __init__(self, email):
        self.email = email


class EventHandler:
    @singledispatchmethod
    def handle_event(event: Event):
        pass  # do nothing

    @handle_event.register
    def _(self, event: UserSignedUp):
        print(f"I will prepare resources for newly signed up user: {event.email}")

    @handle_event.register
    def _(self, event: CustomerCanceledSubscription):
        print(f"I will disable resources for customer:  {event.email}")


events = [
    UserSignedUp(email="[email protected]"),
    CustomerCanceledSubscription(email="[email protected]"),
]

handler = EventHandler()
for event in events:
    handler.handle_event(event)

# I will prepare resources for newly signed up user: [email protected]
# I will disable resources for customer:  [email protected]

Python functools - total_ordering()


Python tip:

You can create an orderable class (assuming the type is totally ordered) with __eq__, one other comparison methods (__ne__, __lt__, __le__, __gt__, __ge__), and the total_ordering decorator.

An example👇

from functools import total_ordering


@total_ordering
class Sample:
    def __init__(self, value):
        self.value = value

    def __lt__(self, other):
        return self.value < other.value


x = Sample(3)
y = Sample(4)

for expression in ["x == y", "x != y", "x < y", "x <= y", "x > y", "x >= y"]:
    result = eval(expression)
    print(f"{expression} is {result}")


"""
x == y is False
x != y is True
x < y is True
x <= y is True
x > y is False
x >= y is False
"""

Without it, you'd need to define all of the comparison methods.

Provision and manage remote Docker hosts with Docker Machine


Docker tip:

Use Docker Machine to quickly provision a remote machine for testing

https://docs.docker.com/machine/

# spin up an ec2 instance with docker engine installed
$ docker-machine create \
    --driver amazonec2 \
    --amazonec2-open-port 8000 \
    --amazonec2-region us-east-1 \
    --amazonec2-instance-type "t2.micro" \
    sandbox

# point your docker daemon at the remote docker engine
$ docker-machine env sandbox
$ eval $(docker-machine env sandbox)

# build the images and spin up the containers on the remote machine
$ docker-compose up -d --build

# ssh certs are auto generated, so you can easily ssh to the remote
$ docker-machine ssh sandbox

# you can also copy files to/ from the machine
$ docker-machine scp docker-machine scp sandbox:~/foo.txt ~/
$ docker-machine scp docker-machine scp foo.tar.gz sandbox:/home/ubuntu

Python functools - cached_property()


Python tip (>=3.8):

You can use cached_property from functools to cache the results of a class attribute for the life of the instance. It's useful if you have a property that is expensive to compute and doesn't need to change.

An example 👇

import io
import pathlib
from functools import cached_property

from pikepdf import Pdf


class PDF:
    def __init__(self, file_bytes):
        self.file_bytes = file_bytes

    @cached_property
    def number_of_pages(self):
        return len(Pdf.open(io.BytesIO(self.file_bytes)).pages)


pdf = PDF(pathlib.Path("file.pdf").read_bytes())
print(pdf.number_of_pages)

What's the difference between select_related and prefetch_related in Django?


Django tip:

Select_related vs prefetch_related

  • Use select_related() on OneToOneField or ForeignKey when you need a single object
  • Use prefetch_related() on ManyToManyFields or reverse relations when you need many objects

👇

class Singer(models.Model):
    first_name = models.CharField(max_length=40)
    last_name = models.CharField(max_length=40)


class Song(models.Model):
    name = models.CharField(max_length=100)
    singer = models.ForeignKey(Singer, related_name="songs")


Song.objects.select_related("singer").all()  # Forward ForeignKey relationship
Singer.objects.select_related("song").all()  # Backward ForeignKey relationship

Python - function overloading with singledispatch


Python tip:

You can use singledispatch to implement different (re)actions based on the first function's argument type

For each type, you register an overloaded implementation

For example, you can handle different events like so:

from functools import singledispatch


class Event:
    pass


class UserSignedUp(Event):
    def __init__(self, email):
        self.email = email


class CustomerCanceledSubscription(Event):
    def __init__(self, email):
        self.email = email


@singledispatch
def handle_event(event: Event):
    pass  # do nothing


@handle_event.register(UserSignedUp)
def _(event: UserSignedUp):
    print(f"I will prepare resources for newly signed up user: {event.email}")


@handle_event.register(CustomerCanceledSubscription)
def _(event: CustomerCanceledSubscription):
    print(f"I will disable resources for customer:  {event.email}")


events = [
    UserSignedUp(email="[email protected]"),
    CustomerCanceledSubscription(email="[email protected]"),
]

for event in events:
    handle_event(event)

# I will prepare resources for newly signed up user: [email protected]
# I will disable resources for customer:  [email protected]

Use the Django auth system with a Single Page App (SPA)


Django tip:

Coupling Django with a front-end framework like React, Vue, or Angular? Use session cookies for auth (even cross-domain).

Why?

  1. It's easier since Django has a powerful built-in auth system
  2. It's safer than using JWTs and localStorage

https://testdriven.io/blog/django-spa-auth/

Caching in Python with lru_cache


Python tip:

Improve the performance of a function with the lru_cache decorator from functools:

@functools.lru_cache(maxsize=128, typed=False)
  • maxsize: max number of stored arguments
  • typed: different types will be cached separately (i.e., 3 != 3.0)
  • function args and kwargs must be hashable
  • perfect for when you need to periodically call an expensive function with the same arguments

Example:

import timeit
from functools import lru_cache

import requests


@lru_cache(maxsize=10, typed=False)
def open_web_page(website_url):
    requests.get(website_url)


without_cache = """
import requests

def open_web_page(website_url):
    requests.get(website_url)

urls = [
    'https://testdriven.io',
    'https://testdriven.io',
    'https://google.com',
    'https://google.com',
]

for url in urls:
    open_web_page(url)
"""

with_cache = """
from functools import lru_cache

import requests

@lru_cache(maxsize=10, typed=False)
def open_web_page(website_url):
    requests.get(website_url)

urls = [
    'https://testdriven.io',
    'https://testdriven.io',
    'https://google.com',
    'https://google.com',
]
for url in urls:
    open_web_page(url)
"""


print(timeit.timeit(without_cache, number=5))
# => 7.195018381

print(timeit.timeit(with_cache, number=5))
# => 3.6599477370000004

Partial functions in Python with partial from functools


Python tip:

You can use partial from functools to freeze a certain number of arguments from a function and create a new, simplified function.

It returns a new partial object which when called will behave like a function called with the positional args and keywords.

An example 👇

from functools import partial

all_users = [
    {"email": "[email protected]"},
    {"email": "[email protected]"},
    {"email": "[email protected]"},
]


def _sort_users_by_email(users, sort_order):
    asc_dsc_to_reverse = {
        "ascending": True,
        "descending": False,
    }

    return sorted(
        users, key=lambda user: user["email"], reverse=asc_dsc_to_reverse[sort_order]
    )


_sort_users_by_email_ascending = partial(_sort_users_by_email, sort_order="ascending")
_sort_users_by_email_descending = partial(_sort_users_by_email, sort_order="descending")


print(_sort_users_by_email_ascending(all_users))
# [{'email': '[email protected]'}, {'email': '[email protected]'}, {'email': '[email protected]'}]

print(_sort_users_by_email_descending(all_users))
# [{'email': '[email protected]'}, {'email': '[email protected]'}, {'email': '[email protected]'}]

Get the unique values from a list of dictionaries


Python tip:

You can use a dictionary comprehension to create a list of dictionaries unique by value on a selected key

users = [
    {"name": "John Doe", "email": "[email protected]"},
    {"name": "Mary Doe", "email": "[email protected]"},
    {"name": "Mary A. Doe", "email": "[email protected]"},
]


print(list({user["email"]: user for user in users}.values()))

"""
[
    {'name': 'John Doe', 'email': '[email protected]'},
    {'name': 'Mary A. Doe', 'email': '[email protected]'}
]

The same key can occur only once inside a dictionary.
Every element from a list is assigned to a key with a value of email.
Each new element with the same email overwrites any existing ones.
Therefore, only one element is left with the same value on the key.

.values() returns a dict_values object containing values from the dictionary,
list() converts the dict_values object back to a list.
"""

Subtracting two lists in Python


Python tip:

To subtract two lists (element by element), you can use a list comprehension and zip.

zip iterates over both lists -> you receive elements with the same index.

The list comprehension then creates a new list containing subtracted values.

👇

first = [1, 5, 7]
second = [5, 3, 0]

print([xi - yi for xi, yi in zip(first, second)])
# => [-4, 2, 7]

Handling missing keys with Python's defaultdict


Python tip:

You can use defaultdict to provide a default value for missing keys when accessing or assigning:

defaultdict(default_factory)

default_factory is a function used to initialize the value for the missing key (None is set by default).

If you pass the list constructor as the default_factory, a defaultdict is created with the values that are list.

For example:

import json
from collections import defaultdict

products = [
    {"name": "Cold beer", "price": 3.5, "category": "Beer"},
    {"name": "Coffee", "price": 1.5, "category": "Warm drinks"},
    {"name": "Cappuccino", "price": 2.0, "category": "Warm drinks"},
    {"name": "Chicken Sandwich", "price": 3.5, "category": "Snacks"},
    {"name": "Vegan Sandwich", "price": 3.5, "category": "Snacks"},
]

products_by_group = defaultdict(list)

for product in products:
    products_by_group[product["category"]].append(product)

print(json.dumps(products_by_group, indent=2))

"""
{
  "Beer": [
    {
      "name": "Cold beer",
      "price": 3.5,
      "category": "Beer"
    }
  ],
  "Warm drinks": [
    {
      "name": "Coffee",
      "price": 1.5,
      "category": "Warm drinks"
    },
    {
      "name": "Cappuccino",
      "price": 2.0,
      "category": "Warm drinks"
    }
  ],
  "Snacks": [
    {
      "name": "Chicken Sandwich",
      "price": 3.5,
      "category": "Snacks"
    },
    {
      "name": "Vegan Sandwich",
      "price": 3.5,
      "category": "Snacks"
    }
  ]
}
"""

Python Counting - finding the most common occurence


Python tip:

You can use Counter from the collections module to find the most common element in a:

  • string - character with the most occurrences
  • dict - key with highest value
  • list - element with the most occurrences

most_common(n) -> returns n most common element

from collections import Counter

counter = Counter("TestDriven.io tutorials")
print(counter.most_common(1))  # character with the most occurrences
# => [('t', 3)]

counter = Counter({"red": 2, "green": 10, "blue": 7})
print(counter.most_common(1))  # key with the highest value
# => [('green', 10)]

counter = Counter([0, 2, 1, 1, 4, 10, 33, 21, 12, 10, 1])
print(counter.most_common(1))  # element with the most occurrences
# => [(1, 3)]

Python type hints - typing.Literal


Python tip:

You can use Literal to indicate that value can be one of the provided literals.

Static type checkers will report an error when the value doesn't match one of the provided literals.

https://mypy.readthedocs.io/en/stable/literal_types.html#literal-types

from typing import Literal

STATUS = Literal["ACTIVE", "DISABLED"]


class User:
    def __init__(self, username: str, status: STATUS):
        self.username = username
        self.status = status


user = User("[email protected]", "CREATED")

"""
mypy example.py
example.py:12: error: Argument 2 to "User" has incompatible type "Literal['CREATED']";
expected "Union[Literal['ACTIVE'], Literal['DISABLED']]"
Found 1 error in 1 file (checked 1 source file)
"""

Python type hints - creating a type alias


Python tip:

You can create a type alias to make your code more readable.

from typing import List

Vector = List[float]


def scale(scalar: float, vector: Vector) -> Vector:
    return [scalar * num for num in vector]

Python Type Hints - typing.Union


Python tip:

Use Union from the typing module to allow any of the listed types.

from typing import Union


def sum_ab(a: Union[int, float], b: Union[int, float]) -> Union[int, float]:
    return a + b

Python Type Hints - typing.TypedDict


Python (>=3.8) tip:

You can subclass TypedDict to create a type for dictionaries with fixed keys.

Static type checking will report an error when there are extra or missing keys.

from typing import TypedDict


class Song(TypedDict):
    name: str
    year: int


song: Song = {"name": "Enter Sandman", "year": 1991, "band": "Metallica"}

"""
mypy song.py
song.py:9: error: Extra key "band" for TypedDict "Song"
Found 1 error in 1 file (checked 1 source file)
"""

Python's final qualifier


Python (>=3.8) tip:

You can use the final decorator to declare that a:

  1. Method should not be overridden
  2. Class should not be subclassed

You can also use the Final annotation to declare that a variable or attribute should not be reassigned, redefined, or overridden. It can be used as indicator to another developer that the given variable should be treated as a constant:

from typing import Final 

_PI: Final =  3.14

A static type check will report an error when it's violated.

https://mypy.readthedocs.io/en/stable/final_attrs.html

from typing import Final, final

# example 1


@final
class Cat:
    @final
    def meow(self):
        return "Meow"


class WildCat(Cat):
    def meow(self):
        return "Grrr"


# mypy cat.py
# error: Cannot inherit from final class "Cat"


# example 2


class Foo:
    @final
    def bar(self):
        return "baz"


def foobar():
    return "foobar"


new = Foo()
new.bar = foobar

# mypy foo.py
# error: Cannot assign to final attribute "bar"


# example 3

favorite_color: Final[str] = "red"
favorite_color = "blue"

# mypy foo.py
# error: Cannot assign to final name "favorite_color"

Problem solving - what have you tried?


When stuck, spend at least 30 minutes but no more than 60 trying to solve the problem before asking for help. Also, when you ask for help make sure you can answer the question, "What have you tried?”.

Python's platform module


Python tip:

You can check the type of machine and platform that your program is running from with platform.machine() and platform.platform(), respectively.

An example👇

import platform

print(platform.machine())
# => x86_64

print(platform.platform())
# => macOS-10.15.6-x86_64-i386-64bit