Collection of Python notes from experience, Stack Overflow, documentation, and also informed by the following very useful sources:
- Code Like a Pythonista: Idiomatic Python
- Transforming Code Into Beautiful Idiomatic Python
- Primer on Python Decorators
- Python Style Guide
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.
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 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
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])
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)
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()
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()
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'
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)]
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 caselen
to sort on the length of the itemsint
orfloat
to sort numerically, as with numeric strings like '2', '123', 35'
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')
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.
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())
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."
)