Skip to content

Instantly share code, notes, and snippets.

@chrswt
Created February 22, 2017 04:06
Show Gist options
  • Save chrswt/1a5c781cec6f0256e3ed7feafe374902 to your computer and use it in GitHub Desktop.
Save chrswt/1a5c781cec6f0256e3ed7feafe374902 to your computer and use it in GitHub Desktop.
Notes and resources for learning Python

Python Notes

Collection of Python notes from experience, Stack Overflow, documentation, and also informed by the following very useful sources:

  1. Code Like a Pythonista: Idiomatic Python
  2. Transforming Code Into Beautiful Idiomatic Python
  3. Primer on Python Decorators
  4. Python Style Guide

Table of Contents

Data Structures

Priority Queues

We can use the queue module to implement a priority queue, the PriorityQueue class typically receives data in tuples of the form: (priority number, data). It also supports the methods empty(), qsize(), get(), and put().

import queue as Q

q = Q.PriorityQueue()

q.put((10, 'ten'))
q.put((1, 'one'))
q.put((5, 'five'))

while not q.empty():
    print q.get()

# (1, 'one') (5, 'five') (10, 'ten')

Trick to implementing this as a max priority queue is to add a negative sign to the priority numbers.

Debugging

Examining data structures

Pretty print makes inspecting data structures much easier, while the locals() function returns a dictionary of all locally-available names.

from pprint import pprint
pprint(locals())

Decorators

Decorators provide a simple syntax for calling higher-order functions. It is a function that takes another function and extends the behavior of the latter function without explicitly modifying it.

It is commonly used for authentication routes or rate limiting (as in the example below):

from time import sleep

def sleep_decorator(function):
    """
    Limits how fast the function is called.
    """

    def wrapper(*args, **kwargs):
        sleep(2)
        return function(*args, **kwargs)

    return wrapper

@sleep_decorator
def print_number(num)
    return num

Dictionaries

Dictionary get method

Instead of initializing dictionary entries before use (i.e. set a property to 0 if it does not exist, incrementing it otherwise), we can use dict.get(key, default).

navs = {}
for (portfolio, equity, position) in data:
    navs[portfolio] = (navs.get(portfolio, 0)
                       + position * prices[equity])

Dictionary setdefault method

For initializing objects within a dictionary, we can use the setdefault method to "set if necessary, then get". For example:

equities = {}
for (portfolio, equity) in data:
    equities.setdefault(portfolio, []).append(equity)

Building a dictionary from two lists

We can use dict and zip to build dictionaries from two lists (or sequences): one list of keys, and another list of values. For example:

given = ['John', 'Eric', 'Terry', 'Michael']
family = ['Cleese', 'Idle', 'Gilliam', 'Palin']
name_list = dict(zip(given, family))

# Can be reversed using the following
name_list.keys()
name_list.values()

File System

Reading lines from text/data files

Files support a next method, as do other iterators (such as lists, tuples, dictionaries (for their keys), and generators.

datafile = open('datafile')
for line in datafile:
    do_something(line)

An even better approach might be:

with open('data.txt') as f:
    data = f.read()

Functions

Optional Arguments (*args and **kwargs)

The "args" and "kwargs" part of the declaration are just there by convention, the real syntax is in the choices to use * and **.

Use *args when you are not sure how many arguments might be passed to your function, and similarly **kwargs is used to handle named arguments that have not been defined in advance. For example:

def foo(required_argument, *args, **kwargs):
    print(required_argument)
    print(args)
    print(kwargs)

foo('required', 'a', 'b', 'c', 'd', named='hello', word='world')
# required argument
# ('a', 'b', 'c', 'd')
# {'named': 'hello', 'word': 'world'}

Note that * and ** can also be used when calling a function, e.g. to unpack a list (or tuple) of items. For example:

def print_three_things(a, b, c):
    print ('a = %s, b = %s, c = %s' % (a, b, c))

random_list = ['testing', 'printing', 'three']
print_three_things(*random_list)
# a = 'testing', b = 'printing', c = 'three'

Lists

List comprehensions

List comprehensions allow for clear and concise syntax for list generation.

# Traditional way
new_list = []
for item in a_list:
    if condition(item):
        new_list.append(fn(item))

# List comprehension
new_list = [fn(item) for item in a_list if condition(item)]

Sorting with keys

An optimal argument to the sort list method is key, which specifies a function of one argument that is used to compute a comparison key from each list element. For example:

def my_key(item):
    return (item[1], item[3])

to_sort.sort(key=my_key)

The function my_key will be called once for each item in the to_sort list, and there are many existing functions that can be used:

  • str.lower to sort alphabetically regardless of case
  • len to sort on the length of the items
  • int or float to sort numerically, as with numeric strings like '2', '123', 35'

Deque data structure

If updating sequences, ensure that you are using the right data structure. The deque data structure in Python is a generalization of stacks and queues (pronounced "deck" and short for "double-ended queue") that support efficient appends and pops from either side of the deque with approximately O(1) performance. For example, instead of:

names = ['raymond', 'rachel', 'matthew', 'roger',
         'betty', 'melissa', 'judith', 'charlie']

del names[0]
names.pop(0)
names.insert(0, 'mark')

We can use the deque data structure (which also has a clear() method):

names = deque(['raymond', 'rachel', 'matthew', 'roger',
               'betty', 'melissa', 'judith', 'charlie'])

del names[0]
names.popleft()
names.appendleft('mark')

Modules

Exporting

To make a simultaneously importable module and exportable script:

if __name__ == '__main__':
    # script code here

When imported, a module's __name__ attribute is set to the module's file name without the ".py". So the code guarded by the if statement will not run when imported. When executed as a script though, the __name__ attribute is set to "main", and the script code will run.

Strings

String interpolation

We can format strings without worrying about matching the interpolation values to the template using advanced string formatting. For example:

values = {'name': name, 'messages': messages}
print('Hello %(name)s, you have %(messages)i '
      'messages' % locals())

Multi-line strings

Use parentheses around elements, Python will join the next line until parentheses are closed. This works well for splitting multi-line logic, but also for long strings.

my_long_string = (
    "Some very long line..."
    "Another very long line..."
    "Will all be concatenated nicely."
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment