Python Tricks Roundup

By: Cam Wohlfeil
Published: 2019-04-22 1130 EDT
Modified: 2019-04-22 1220 EDT
Category: Programming
Tags: python

I subscribed to Dan Bader's mailing lists in January so you don't have to (you should though, it's decent). Here's the tricks I got from it so far.

Python 3 has a std lib module for working with IP addresses:

import ipaddress
ipaddress.ip_address('192.168.1.2')
# IPv4Address('192.168.1.2')
ipaddress.ip_address('2001:af3::')
# IPv6Address('2001:af3::')

The lambda keyword in Python provides a shortcut for declaring small and anonymous functions:

add = lambda x, y: x + y
add(5, 3)
# 8

What makes them different from regular functions? Lambdas are function expressions:

(lambda x, y: x + y)(5, 3)
# 8

Lambda functions are single-expression functions that are not necessarily bound to a name (they can be anonymous). Lambda functions can't use regular Python statements and always include an implicit return statement.

@classmethod vs @staticmethod vs "plain" methods

class MyClass:
    def method(self):
        """
        Instance methods need a class instance and
        can access the instance through `self`.
        """
        return 'instance method called', self

    @classmethod
    def classmethod(cls):
        """
        Class methods don't need a class instance.
        They can't access the instance (self) but
        they have access to the class itself via `cls`.
        """
        return 'class method called', cls

    @staticmethod
    def staticmethod():
        """
        Static methods don't have access to `cls` or `self`.
        They work like regular functions but belong to
        the class's namespace.
        """
        return 'static method called'

All methods types can be called on a class instance:

obj = MyClass()
obj.method()
# ('instance method called', <MyClass instance at 0x1019381b8>)
obj.classmethod()
# ('class method called', <class MyClass at 0x101a2f4c8>)
obj.staticmethod()
# 'static method called'

Calling instance methods fails if we only have the class object:

MyClass.classmethod()
# ('class method called', <class MyClass at 0x101a2f4c8>)
MyClass.staticmethod()
# 'static method called'
MyClass.method()
# TypeError: 
#       "unbound method method() must be called with MyClass "
#       "instance as first argument (got nothing instead)"

You can use Python's built-in dis module to disassemble functions and inspect their CPython VM bytecode:

def greet(name):
    return 'Hello, ' + name + '!'
greet('Cam')
# 'Hello, Cam!'
import dis
dis.dis(greet)
# 2   0 LOAD_CONST     1 ('Hello, ')
#     2 LOAD_FAST      0 (name)
#     4 BINARY_ADD
#     6 LOAD_CONST     2 ('!')
#     8 BINARY_ADD
#     10 RETURN_VALUE

When To Use __repr__ vs __str__?

Emulate what the std lib does:

import datetime
today = datetime.date.today()

Result of str should be readable:

str(today)
# '2017-02-02'

Result of repr should be unambiguous:

repr(today)
# 'datetime.date(2017, 2, 2)'

Python interpreter sessions use __repr__ to inspect objects:

today
# datetime.date(2017, 2, 2)

itertools.permutations() generates permutations for an iterable.

Time to brute-force those passwords!

import itertools
for p in itertools.permutations('ABCD'):
    print(p)
# ('A', 'B', 'C', 'D')
# ('A', 'B', 'D', 'C')
# ('A', 'C', 'B', 'D')
# ('A', 'C', 'D', 'B')
# ('A', 'D', 'B', 'C')
# ('A', 'D', 'C', 'B')
# ('B', 'A', 'C', 'D')
# ('B', 'A', 'D', 'C')
# ('B', 'C', 'A', 'D')
# ('B', 'C', 'D', 'A')
# ('B', 'D', 'A', 'C')
# ('B', 'D', 'C', 'A')
# ('C', 'A', 'B', 'D')
# ('C', 'A', 'D', 'B')
# ('C', 'B', 'A', 'D')
# ('C', 'B', 'D', 'A')
# ('C', 'D', 'A', 'B')
# ('C', 'D', 'B', 'A')
# ('D', 'A', 'B', 'C')
# ('D', 'A', 'C', 'B')
# ('D', 'B', 'A', 'C')
# ('D', 'B', 'C', 'A')
# ('D', 'C', 'A', 'B')
# ('D', 'C', 'B', 'A')

collections.Counter lets you find the most common elements in an iterable:

import collections
c = collections.Counter('helloworld')
c
# Counter({'l': 3, 'o': 2, 'e': 1, 'd': 1, 'h': 1, 'r': 1, 'w': 1})
c.most_common(3)
# [('l', 3), ('o', 2), ('e', 1)]

Python's list slice syntax can be used without indices for a few fun and useful things:

You can clear all elements from a list:

lst = [1, 2, 3, 4, 5]
del lst[:]
lst
# []

You can replace all elements of a list without creating a new list object:

a = lst
lst[:] = [7, 8, 9]
lst
# [7, 8, 9]
a
# [7, 8, 9]
a is lst
# True

You can also create a (shallow) copy of a list:

b = lst[:]
b
# [7, 8, 9]
b is lst
# False

Python 3.5+ supports 'type annotations' that can be used with tools like Mypy to write statically typed Python:

Note: these are actually just type hints, and right now only developer tools use them, but the interpreter might make use of them eventually.

def my_add(a: int, b: int) -> int:
    return a + b

Python's list comprehensions are awesome.

vals = [expression 
        for value in collection 
        if condition]

This is equivalent to:

vals = []
for value in collection:
    if condition:
        vals.append(expression)

Example:

even_squares = [x * x for x in range(10) if not x % 2]
even_squares
# [0, 4, 16, 36, 64]

Python has a HTTP server built into the standard library. This is super handy for previewing websites.

Python 3.x: $ python3 -m http.server

Python 2.x: $ python -m SimpleHTTPServer 8000

This will serve the current directory at http://localhost:8000.

Because Python has first-class functions they can be used to emulate switch/case statements

def dispatch_if(operator, x, y):
    if operator == 'add':
        return x + y
    elif operator == 'sub':
        return x - y
    elif operator == 'mul':
        return x * y
    elif operator == 'div':
        return x / y
    else:
        return None

def dispatch_dict(operator, x, y):
    return {
        'add': lambda: x + y,
        'sub': lambda: x - y,
        'mul': lambda: x * y,
        'div': lambda: x / y,
    }.get(operator, lambda: None)()

dispatch_if('mul', 2, 8)
# 16
dispatch_dict('mul', 2, 8)
# 16
dispatch_if('unknown', 2, 8)
# None
dispatch_dict('unknown', 2, 8)
# None

Functions are first-class citizens in Python.

They can be passed as arguments to other functions, returned as values from other functions, and assigned to variables and stored in data structures.

def myfunc(a, b):
    return a + b
funcs = [myfunc]
funcs[0]
# <function myfunc at 0x107012230>
funcs[0](2, 3)
# 5

"is" vs "=="

a = [1, 2, 3]
b = a

a is b
# True
a == b
# True

c = list(a)

a == c
# True
a is c
# False

is expressions evaluate to True if two variables point to the same object.

== evaluates to True if the objects referred to by the variables are equal.

Python Riddle: 👻 it is a mystery

What will this expression evaluate to?

{True: 'yes', 1: 'no', 1.0: 'maybe'}
# <span class="spoiler">{True: 'maybe'}</span>

This is confusing until you break it down step by step, as seen in the link below.

Why Python Is Great: In-place value swapping

Let's say we want to swap the values of a and b...

a = 23
b = 42

The "classic" way to do it with a temporary variable:

tmp = a
a = b
b = tmp

Python also lets us se this short-hand: a, b = b, a

The "timeit" module lets you measure the execution time of small bits of Python code

import timeit
timeit.timeit('"-".join(str(n) for n in range(100))',
                  number=10000)
# 0.3412662749997253
timeit.timeit('"-".join([str(n) for n in range(100)])',
                  number=10000)
# 0.2996307989997149
timeit.timeit('"-".join(map(str, range(100)))',
                  number=10000)
# 0.24581470699922647

Why Python Is Great: Function argument unpacking

def myfunc(x, y, z):
    print(x, y, z)

tuple_vec = (1, 0, 1)
dict_vec = {'x': 1, 'y': 0, 'z': 1}

myfunc(*tuple_vec)
# 1, 0, 1
myfunc(**dict_vec)
# 1, 0, 1

Why Python is Great: Namedtuples

Using namedtuple is way shorter than defining a class manually:

 from collections import namedtuple
 Car = namedtuple('Car', 'color mileage')

Our new "Car" class works as expected:

my_car = Car('red', 3812.4)
my_car.color
# 'red'
my_car.mileage
# 3812.4

We get a nice string repr for free:

my_car
# Car(color='red' , mileage=3812.4)

Like tuples, namedtuples are immutable:

my_car.color = 'blue'
# AttributeError: "can't set attribute"

The get() method on dicts and its "default" argument

name_for_userid = {
    382: "Alice",
    590: "Bob",
    951: "Dilbert",
}

def greeting(userid):
    return "Hi %s!" % name_for_userid.get(userid, "there")

greeting(382)
# "Hi Alice!"
greeting(333333)
# "Hi there!"

How to sort a Python dict by value

(== get a representation sorted by value)

xs = {'a': 4, 'b': 3, 'c': 2, 'd': 1}
sorted(xs.items(), key=lambda x: x[1])
# [('d', 1), ('c', 2), ('b', 3), ('a', 4)]

Or:

import operator
sorted(xs.items(), key=operator.itemgetter(1))
# [('d', 1), ('c', 2), ('b', 3), ('a', 4)]

Different ways to test multiple flags at once in Python

x, y, z = 0, 1, 0
if x == 1 or y == 1 or z == 1:
    print('passed')
if 1 in (x, y, z):
    print('passed')

These only test for truthiness:

if x or y or z:
    print('passed')
if any((x, y, z)):
    print('passed')

How to merge two dictionaries in Python 3.5+

x = {'a': 1, 'b': 2}
y = {'b': 3, 'c': 4}
z = {**x, **y}
z
# {'c': 4, 'a': 1, 'b': 3}

In Python 2.x you could use this:

z = dict(x, **y)
z
# {'a': 1, 'c': 4, 'b': 3}

In these examples, Python merges dictionary keys in the order listed in the expression, overwriting duplicates from left to right.

References