Python

13 Oct 2023 - Houston

Contents
  1. Fundamentals
    1. Types
      1. Type reflection
      2. Identity
    2. Mutable and immutable objects
    3. Built-in functions
      1. len
      2. range
      3. max and min
      4. pow
      5. sorted
    4. Input/output
      1. Command-line args
      2. Standard Input/Output
      3. File opening and reading
      4. File seeking
      5. Iteratively read files
      6. Tempfile
      7. Manipulating zip files
    5. Variadic and keyword arguments
    6. Bitwise operations
    7. Bytes and Byte arrays
  2. Regular Expressions
    1. re.search
    2. re.sub
    3. re.split
    4. re.match
    5. re.fullmatch
    6. re.findall
    7. re.finditer
  3. Collections
    1. Slicing
    2. Lists, tuples, and dictionaries
    3. Dictionaries
    4. Sequences: Tuples and Lists
  4. Strings
    1. Predicate methods
    2. Splitting, joining, formatting
      1. Splitting
      2. Joining
      3. Formatting
    3. Format strings: Formatting
  5. Comprehensions
    1. List comprehension
    2. Dict comprehensions
    3. Set comprehensions
    4. Generator comprehensions
  6. Sequences and iteration
    1. Iteration
      1. Loops: while, for, enumerate
      2. Higher-order functions: map, filter, reduce
      3. Convolution zip:
      4. Higher-order predicates: any and all
    2. Generator functions: Creating sequences
  7. Decorators
    1. Use case: Creating accessor properties for instance variables
    2. Use case: arbitrary decoration
  8. Math
    1. Constants
    2. Trigonometry
    3. Floor and ceiling
    4. Factorial, sqrt, gcd
    5. Random
      1. randrange, choice, sample, shuffle
      2. randrange, randint
    6. Statistics
      1. Measures of central tendency: Mean, median, mode
      2. Measures of spread: variance, stdev
  9. More Collections
    1. defaultdict
      1. Use case: counting
      2. Use case: Nested counting
    2. namedtuple
    3. Counter
  10. Iteration tools: itertools
      1. Infinite counting: count
      2. Infinite cycling: cycle
      3. Infinite repeating: repeat
      4. Permutations: permutations
      5. Combinations: combinations
      6. starmap: map a function over a list of arg lists
      7. Convolution (exhaustive): zip_longest
  11. Functional tools: functools
    1. functools.wraps
    2. functools.lru_cache
    3. functools.partial
  12. Date/Time, Calendars, Timers
    1. Current time, formatting dates and times
    2. Calculating future times
    3. Calendars
    4. Creating timers
  13. Text munging
    1. HTML Parsing: html.parser
    2. Text wrapping: textwrap
  14. HTTP: urllib.request, json
    1. HTTP, urllib, JSON
  15. Batteries-included web-scraping: requests and bs4
  16. Databases: sqlite3
  17. Testing: unittest
  18. Web development: flask
    1. Packages and utilities
    2. Routes / Controller
    3. Models
    4. Forms
  19. Geocoding and geoplotting: geopy and matplotlib
    1. Geocoding
    2. Plotting
  20. More libraries
  21. Classes
    1. Enums
      1. Can enforce uniquness
      2. Can assign values automatically
    2. Class string functions
      1. str, format, repr, bytes
    3. Computed attributes
    4. Custom class numerical operators
  22. Logging
    1. Logging levels
    2. Initializing
  23. Generators
    1. Refresher
    2. Generator expression / generator comprehension
    3. Generator function
    4. Generator pipelines
    5. Context managers: with
  24. Coroutines
    1. Properties
    2. Minimal Example
  25. Threading
    1. Minimal Example: multiprocessing.dummy
      1. Multi-threaded
      2. Comparison to single-threaded implementation
  26. Multiprocessing
    1. Minimal Example: Pool (preferred)
    2. Minimal Example: Process

Fundamentals

Types

Type reflection

class Car:
    pass

class Truck(Car):
    pass

car = Car()
truck = Truck()

print(type(truck) == type(car))
print(isinstance(car, Car))
print(isinstance(car, Truck))
print(isinstance(truck, Truck))
print(isinstance(truck, Car))
False
True
False
True
True
w = 42
y = ['42']
z = ['42']

print(type(w))
print(type(y))
print(type(z[0]))
print(type(True))
print(type(33.5))
<class 'int'>
<class 'list'>
<class 'str'>
<class 'bool'>
<class 'float'>

Identity

  • Numbers, floats, strings are immutable

    w = 42
    x = 42
    y = ['42']
    z = ['42']
    
    print(id(w))
    print(id(x))
    
    print(id(34.3))
    print(id(34.3))
    
    print(id(y[0]))
    print(id(z[0]))
    
    4383542560
    4383542560
    4385010312
    4385010312
    4385841648
    4385841648
    
  • Lists and other container objects are not

    w = 42
    x = 42
    y = ['42']
    z = ['42']
    
    print(id(y))
    print(id(z))
    
    4563076296
    4563259016
    
  • Comparison

    • By value

      • Use ==, !=, <, >, <=, >=
    • By identity

      • Use is, is not

Mutable and immutable objects

print('immutable:')
immutable = 42
print(id(immutable))
immutable += 1
print(id(immutable))
immutable -= 1
print(id(immutable))

print('\nimmutable:')
immutable = 'str'
print(id(immutable))
immutable += '1'
print(immutable)
print(id(immutable))
immutable = immutable.replace('1', '')
print(id(immutable))

print('\nmutable:')
mutable = [1, 2, 3]
print(mutable)
print(id(mutable))
mutable.append(4)
print(mutable)
print(id(mutable))
mutable.append(5)
print(mutable)
print(id(mutable))
immutable:
4430617888
4430617920
4430617888

immutable:
4432005024
str1
4433423528
4434276624

mutable:
[1, 2, 3]
4434411656
[1, 2, 3, 4]
4434411656
[1, 2, 3, 4, 5]
4434411656

Built-in functions

len

name = "jebediah"
return name.__len__()
8
name = "jebediah"
return len(name)
8
a_string = "abc"
for char in a_string:
    print(char)
a
b
c

range

# 0-indexed range, given n is non-inclusive least upper bound
for n in range(3):
    print(n)
0
1
2
# specify start n with two args
for n in range(3, 5):
    print(n)
3
4

max and min

names = ["abe", "Abe"]
return min(names)
'Abe'
names = ['abe', 'Abe']
return max(names)
'abe'
names = ['kat', 'joe', 'ron']
return max(names)
'ron'

pow

return pow(2, 3)
8
return 2**3
8

sorted

names = ['sue', 'jerry', 'linda']
return sorted(names)
['jerry', 'linda', 'sue']
names = ['sue', 'jerry', 'linda']
return sorted(names, reverse=True)
['sue', 'linda', 'jerry']
names = ['sue', 'jerry', '  linda']
return sorted(names)
['  linda', 'jerry', 'sue']
names = ['sue', 'jerry', '  linda']
return sorted(names, key=str.strip)
['jerry', '  linda', 'sue']
names = {'sue': 1, 'jerry': 4, 'linda': 55}
return sorted(names)
['jerry', 'linda', 'sue']
names = [('sue', 100), ('jerry', 4), ('linda', 55)]
return sorted(names, key=lambda s: s[1])
[('jerry', 4), ('linda', 55), ('sue', 100)]

Input/output

Command-line args

  • Print and remove arguments

    import sys
    sys.argv=['script.py', 135, 33]
    
    print(len(sys.argv))
    print(sys.argv)
    sys.argv.remove(sys.argv[0])
    print(sys.argv)
    
    3
    ['script.py', 135, 33]
    [135, 33]
    

Standard Input/Output

try:
    color = input("What's your favorite color? ")
except Exception:
    color = 'red'

print(color)
What's your favorite color? red

File opening and reading

  • File I/O: Reading by lines

    with open('file.txt', 'r') as f:
        for line in f:
            pass
            # ...
    
  • File I/O: Reading by chunks

    chunksize = 50_000
    with open(filename) as f:
        while True:
            chunk = f.read(chunksize)
            if not chunk: break # if None, file has been exhausted, so break
            print(line) # (or yield / do whatever)
    

File seeking

Pointer keeps track of position in file. Closing and re-opening resets the pointer.

filename = 'data.md'
data_file = open(filename, 'w')
data_file.write(
    """
    File Seeking
    ============

    This is markdown
    ----------------

    Test one two.
    """)
data_file.close()

data_file = open(filename, 'r')
print("reading:")
print(data_file.read(30))
print("---  ---  ---")
print(data_file.read(30))
print("---  resetting pointer  ---")
print(data_file.seek(0))
print("reading:")
print(data_file.read(30))

data_file.close()
import os
os.remove(filename)
reading:

    File Seeking
    ============

    Th
    ---  ---  ---
    is is markdown
    ---------------
    ---  resetting pointer  ---
    0
reading:

    File Seeking
    ============

    Th

Iteratively read files

filename = './data.txt'
file_data = open(filename, 'w')
file_data.write('one\ntwo\nthree')
file_data.close()

file_data = open(filename, 'r')
print(file_data.readline().rstrip())
print(file_data.readline().rstrip())
print("-- reset --")
file_data.seek(0)
print(file_data.readline().rstrip())

print("-- reset --")
file_data.seek(0)
for line in file_data:
    print(line.replace("o", "O").rstrip())
    file_data.close()

import os
os.remove(filename)
one
two
-- reset --
one
-- reset --
One
twO
three

Tempfile

import tempfile
tempf = tempfile.TemporaryFile()

# note the 'b' to turn str into a binary literal
tempf.write(b"save this number: 4334433")
tempf.seek(0)

print(tempf.read())
tempf.close()
b'save this number: 4334433'

Manipulating zip files

import zipfile as zf

zipf = zf.ZipFile('Archive.zip', 'r')
print(zipf.namelist())
print(zipf.getinfo('filename.txt'))

# metadata
zipf.infolist()

# access to files in zip dir
print(zipf.read('filename.txt'))
with zipf.open('filename.txt') as f:
    print(f.read())

# extracting files
zipf.extract('purchased.txt')
zipf.extractall()

# Closing the zip
zipf.close()

Variadic and keyword arguments

def a_named_func(arg1, arg2=None, *rest, **kwargs):
    print(arg1)
    print(arg2)
    print(rest) # tuple
    print(kwargs) # dict

a_named_func(1, 2, 3, 4, 5, six=6, seven=7, eight=8)
a_named_func(1, six=6, eight=8, seven=7)
1
2
(3, 4, 5)
{'six': 6, 'seven': 7, 'eight': 8}
1
None
()
{'six': 6, 'eight': 8, 'seven': 7}

Bitwise operations

def bin(n): return '{:08b}'.format(n)
print('%s (%s) [5]' % (bin(5) , ()))

x, y = 0x55, 0xaa

print('---------')
print("%s (%s) [x]" % (bin(x) , (x)))
print("%s (%s) [y]" % (bin(y) , (y)))

print('- - - - -')
print("%s (%s) [or]" % (bin(x | y) , (x | y)))
print("%s (%s) [and]" % (bin(x & y) , (x & y)))
print("%s (%s) [xor]" % (bin(x ^ y) , (x ^ y)))

print('---------')
print("%s (%s) [x ^ 0]" % (bin(x ^ 0) , (x ^ 0)))
print("%s (%s) [x ^ x]" % (bin(x ^ x) , (x ^ x)))

print('---------')
ten = 10
print("%s (%s) [ten]" % (bin(ten) , (ten)))
print("%s (%s) [ten << 1]" % (bin(ten << 1) , (ten << 1)))
print("%s (%s) [ten << 2]" % (bin(ten << 2) , (ten << 2)))
00000101 (()) [5]
---------
01010101 (85) [x]
10101010 (170) [y]
- - - - -
11111111 (255) [or]
00000000 (0) [and]
11111111 (255) [xor]
---------
01010101 (85) [x ^ 0]
00000000 (0) [x ^ x]
---------
00001010 (10) [ten]
00010100 (20) [ten << 1]
00101000 (40) [ten << 2]

Bytes and Byte arrays

utf8string = 'Ďelta ēpsilon'
print(ord(utf8string[0]))
print(ord('z'))
print(chr(127))

outbytes = bytearray()
for char in utf8string:
    if ord(char) < 127:
        outbytes.append(ord(char))
    else:
        outbytes += bytes('&#{:04d};'.format(ord(char)), encoding='utf_8')
        outstr = str(outbytes, encoding='utf_8')
        print(outstr)
270
122

&#0270;elta &#0275;psilon

Regular Expressions

  1. import re
  2. compile a pattern (e.g. r'[:alpha:]')
    1. Pre-compiling allows for efficient re-use
  3. use the pattern in re functions

re.search

import re
letters = re.compile('[a-z]')
numbers = re.compile('[0-9]')

# note the object-oriented style here (re_object.search)
letters_match = letters.search('one two three')
print(letters_match)
print(letters_match[0])

# note the functional style here (re.search)
digits_match = re.search(numbers, 'one two three')
print(digits_match)
<_sre.SRE_Match object; span=(0, 1), match='o'>
o
None

re.sub

import re
string = 'hellosam'
newstr1 = re.sub('[aeiou]', '+', string)
print(newstr1)

vowels_regex = re.compile('(he|am)')
newstr2 = vowels_regex.sub('+', string)
print(newstr2)
h+ll+s+m
+llos+

re.split

import re
elements = re.split('\W+', 'one=two  -three+four five', maxsplit=3, flags=re.IGNORECASE)
print(elements)
['one', 'two', 'three', 'four five']

re.match

import re
match1 = re.match('(A)+', 'aaabcabcabc', flags=re.IGNORECASE)
print(match1.group(0))
print(match1.group(1))

match2 = re.match('(F)+', 'aaabcabcabc', flags=re.IGNORECASE)
print(match2)
aaa
a
None

re.fullmatch

import re
matches_full1 = re.fullmatch('a+', 'aaabcabcabc', re.IGNORECASE)
print(matches_full1)
matches_full2 = re.fullmatch('(a+bc)+', 'aaabcabcabc', re.IGNORECASE)
print(matches_full2[0])
print(matches_full2.group(0))
print(matches_full2.group(1))
None
aaabcabcabc
aaabcabcabc
abc

re.findall

import re
matches = re.findall('abc', 'aaaBCaBCaBC', flags=re.IGNORECASE)
print(matches)
['aBC', 'aBC', 'aBC']

re.finditer

import re
matches = re.finditer('abc', 'aaaBCaBCaBC', flags=re.IGNORECASE)
print(matches)
print([match[0] for match in matches])
<callable_iterator object at 0x10926e208>
['aBC', 'aBC', 'aBC']

Collections

Slicing

a_str = "0123456789"
print(a_str[0])
print(a_str[5:])
print(a_str[0:5])
print(a_str[1:5])
0
56789
01234
1234
a_str = "0123456789"
print(a_str[1:-1])
print(a_str[-4:-1])
print(a_str[-4:])
print(a_str[-4])
12345678
678
6789
6
nums = list(range(10))
print(nums)

# [start_index : end_index (non-inclusive) : every nth element]
print(nums[5::2])

nums[:] = range(10, 20)
print(nums)
print(nums[5::2])
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[5, 7, 9]
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
[15, 17, 19]

Lists, tuples, and dictionaries

  • Tuples are immutable (and hence faster), lists mutable.
  • Dictionaries are unordered, but you can iterate over them by using a dict view (potentially sorted for maximum consistency across Python implementations)
d = {'one': 1, 'two': 2, 'three': 3}

for k in d.keys():
    print('%s : %s' % (k, d[k]))
    print('-------------')
for k in sorted(d.keys()):
    print('%s : %s' % (k, d[k]))
    print('-------------')
for k, v in d.items():
    print('%s : %s' % (k, v))
one : 1
two : 2
three : 3
-------------
one : 1
three : 3
two : 2
-------------
one : 1
two : 2
three : 3
# defining with kw args
print(dict(one = 1, two = 2, three = 3))
{'one': 1, 'two': 2, 'three': 3}

Dictionaries

Merging / initializing from another

one = dict(one=1, two=2, three=3)
print(one)
print('---------------')

two = dict(**one, four=4, five=5)
print(two)
two = { **one, 'four': 4, 'five': 5 }
print(two)

print('\nmerging and overwriting')
print('---------------')
two_a = dict(three='three', four='four')
three = { **two, **two_a }
print(three)
{'one': 1, 'two': 2, 'three': 3}
---------------
{'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5}
{'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5}

merging and overwriting
---------------
{'one': 1, 'two': 2, 'three': 'three', 'four': 'four', 'five': 5}

Accessing: Prefer the get method to avoid exceptions, set default return value

x = {}
try:
    x['three']
except:
    print('not there')

print(x.get('three'))
print(x.get('three', '3'))
not there
None
3

Deleting: Use pop to return the value removed, otherwise del

x = dict(one=1, two=2, three=3, four=4)
del x['one']
print(x)
print(x.pop('two'))
print(x)
{'two': 2, 'three': 3, 'four': 4}
2
{'three': 3, 'four': 4}

Sequences: Tuples and Lists

Tuples are created with the comma operator, NOT with parens.

tup1 = (1)
print(tup1)
print(type(tup1))
tup1 = (1,)
print(tup1)
print(type(tup1))
1
<class 'int'>
(1,)
<class 'tuple'>

Common sequences methods work on tuples as on lists. Tuples are immutable. Lists are mutable.

tup = 1, 2, 3, 4, 5
print(tup)
print(tup[0])
print(tup[-1])
print(len(tup))
print(max(tup))
(1, 2, 3, 4, 5)
1
5
5
5

Built-in methods for sequences:

tup = tuple(range(10))
print(tup)
print(len(tup))
print(tup.count(5))
print(tup.index(5))
print(10 in tup)
print(4 in tup)
(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
10
1
5
False
True

Strings

Predicate methods

print(''.isalnum())
print('this is a string'.isalnum())
print('--------------')
print('this2isastring'.isalnum())
print('this2isastring'.isalpha())
print('thisisastring'.isalpha())
print('--------------')
print('1234'.isdigit())
print('--------------')
print('1234'.isprintable())
print('1234\n'.isprintable())
False
False
--------------
True
False
True
--------------
True
--------------
True
False

Splitting, joining, formatting

Splitting

print('This is a string of words'.split())
print('This is a string of words'.split('i'))

import re
vowels_and_spaces = re.compile('[aeiou ]+')
print(vowels_and_spaces.split('This is a string of words'))
['This', 'is', 'a', 'string', 'of', 'words']
['Th', 's ', 's a str', 'ng of words']
['Th', 's', 's', 'str', 'ng', 'f', 'w', 'rds']

Joining

words = ['this', 'are', 'some', 'words']
print(' '.join(words))

words = ['this', 'are', 'some', 'words']
print(':'.join(words))
this are some words
this:are:some:words

Formatting

string = '   THIS is a string   '
print('|{}|'.format(string.upper()))
print('|{}|'.format(string.lower()))
print('|{}|'.format(string.rstrip()))
print('|{}|'.format(string.lstrip()))
print('|{}|'.format(string.strip()))
|   THIS IS A STRING   |
|   this is a string   |
|   THIS is a string|
|THIS is a string   |
|THIS is a string|

To chomp newlines, pass a newline to rstrip:

string_with_newline = 'this needs to be chomped\n'
print('<{}>'.format(string_with_newline))
print('<{}>'.format(string_with_newline.rstrip('\n')))
<this needs to be chomped
>
<this needs to be chomped>

To center characters in a string

centered_string = 'memento mori'.center(30, ' ')
print('<{}>'.format(centered_string))
<         memento mori         >

Format strings: Formatting

Use str.format to interpolate in idiomatic Python 3.

# (% operator is considered obscelescent)
print('This is a string {} {}'.format(2, 44))

print('\nposition numbering')
print('-------')
print('{0} {1}'.format(2, 44))
print('{1} {0}'.format(2, 44))

print('\nleft-pad with zeros')
print('-------')
print('%02d %d' % (2, 44))
print('{0:02d} {1}'.format(2, 44))

print('\nright-align')
print('-------')
print('|{:>5}|{}|{}|'.format(2, 3, 5))
print('|{:>5}|{:>5}|{:>5}|'.format(2, 3, 5))

print('\nleft-align')
print('-------')
print('|{:<5}|{}|{}|'.format(2, 3, 5))
print('|{:<5}|{:<5}|{:<5}|'.format(2, 3, 5))

print('\ncenter-align')
print('-------')
print('|{:^5}|{}|{}|'.format(2, 3, 5))
print('|{:^5}|{:^5}|{:^5}|'.format(2, 3, 5))

print('\ntruncating and padding')
print('-------')
print('|{:>10.6}|'.format('this is a rather long string'))

print('\nformatting numbers')
print('-------')
print('|{:^7d}|{:7.2f}|{:07.2f}|'.format(2, 3, 5))
This is a string 2 44

position numbering
-------
2 44
44 2

left-pad with zeros
-------
02 44
02 44

right-align
-------
|    2|3|5|
|    2|    3|    5|

left-align
-------
|2    |3|5|
|2    |3    |5    |

center-align
-------
|  2  |3|5|
|  2  |  3  |  5  |

truncating and padding
-------
|    this i|

formatting numbers
-------
|   2   |   3.00|0005.00|

Comprehensions

List comprehension

Useful for transforming collections. More Pythonic than equivalent higher-order functions.

collection = [1, 2, 3, 4]

successors = [x + 1 for x in collection]
print(successors)

odds = [x for x in collection if x % 2 is not 0]
print(odds)
[2, 3, 4, 5]
[1, 3]

Dict comprehensions

elements = dict(one=1, two=2, three=3, four=4)

successors = {k: v + 1 for k, v in elements.items()}
print(successors)

odds = {k: v for k, v in elements.items() if v % 2 is not 0}
print(odds)
{'one': 2, 'two': 3, 'three': 4, 'four': 5}
{'one': 1, 'three': 3}

Set comprehensions

elements = [1, 1, 1, 2, 3, 4, 5, 6, 6, 6, 7, 8, 9]
uniquified_odds = { n for n in elements if n % 2 is not 0 }
print(uniquified_odds)
{1, 3, 5, 7, 9}

Generator comprehensions

first_evens = (i for i in range(11) if i % 2 is 0)
print(first_evens)
print(first_evens.__next__())
print(first_evens.__next__())
print(first_evens.__next__())
print('---')

for n in first_evens:
    print(n)
<generator object <genexpr> at 0x1076d4678>
0
2
4
---
6
8
10

Sequences and iteration

Iteration

Loops: while, for, enumerate

  • while loop

    n = 0
    while n < 3:
        n += 1
        print(n)
    
    1
    2
    3
    
  • for-iteration

    for n in range(1, 4):
        print(n)
    
    1
    2
    3
    
  • Enumerated iteration

    str = 'this is a string'
    
    for char in str:
        if char == 's':
            print(char)
    
    print('\nenumerated:')
    for (idx, char) in enumerate(str):
        if idx < 5:
            print(char)
    
    s
    s
    s
    
    enumerated:
    t
    h
    i
    s
    

Higher-order functions: map, filter, reduce

More declarative than bare loops.

collection = [1, 2, 3, 4]

successors = list(map(lambda x: x + 1, collection))
print(successors)

odds = list(filter(lambda x: x % 2 is not 0, collection))
print(odds)

from functools import reduce
summation = reduce(lambda x, y: x + y, collection)
print(summation)
[2, 3, 4, 5]
[1, 3]
10

Convolution zip:

Output length determined by length of the shortest iterable. See itertools.zip_longest for an alternative version that exhausts all iterables.

tuples = list(zip(range(5), range(15), reversed(range(10))))
print(tuples)
[(0, 0, 9), (1, 1, 8), (2, 2, 7), (3, 3, 6), (4, 4, 5)]

Higher-order predicates: any and all

  • Basic usage

    Can be combined with higher-order functions or list comprehensions efficiently (both return iterators instead of eagerly building lists)

    nums = list(range(11))
    
    print(any(map(lambda n: n % 2 is 0, nums)))
    print(any(n % 2 is 0 for n in nums))
    
    print(all(map(lambda n: n % 2 is 0, nums)))
    print(all(n % 2 is 0 for n in nums))
    
    True
    True
    False
    False
    
  • With keyword args for concise filtering

    from collections import namedtuple
    from pprint import pprint
    
    Dog = namedtuple('dog', 'name age preferred_pronoun')
    records = ['rover1,4,he', 'rover2,2,she', 'rover3,3,she', 'rover4,4,he']
    dogs = [Dog(*record.split(',')) for record in records]
    
    
    def find_dogs(**kwargs):
        return [
                dog for dog in dogs
                if all(getattr(dog, key) == value for key, value in kwargs.items())
            ]
    
    dogs_list = find_dogs(age='4', preferred_pronoun='he')
    pprint(dogs_list)
    
    [dog(name='rover1', age='4', preferred_pronoun='he'),
     dog(name='rover4', age='4', preferred_pronoun='he')]
    

Generator functions: Creating sequences

Use to create iterators

import math

def is_prime(n):
    if n < 2:
        return False
    for i in range(2, math.floor(math.sqrt(n)) + 1):
        if i % 2:
            return False
    return True

def primes(lower_bound=3, upper_bound=50):
    while lower_bound <= upper_bound:
        if is_prime(lower_bound):
            yield lower_bound
        lower_bound += 2

print('primes:')
for prime in primes(upper_bound=10, lower_bound=5):
    print(prime)
primes:
5
7

Decorators

Use case: Creating accessor properties for instance variables

class DogStack:
    def __init__(self, **kwargs):
        self._label = kwargs.get('label', 'dogstack1')
        self._stack = []

    @property
    def stack(self):
        return self._stack

    def push(self, *items):
        self.stack.extend(items)
        return self

    @property
    def last(self):
        if len(self.stack) is 0:
            return None
        return self.stack[-1]

    @last.setter
    def last(self, element):
        if len(self.stack) is 0:
            self.stack.append(element)
        else:
            self.stack[-1] = element

    @last.deleter
    def last(self):
        self.stack.pop()

dog = DogStack()
print(dog.last)
dog.push('one', 'two', 'three').push('four')
print(dog.stack)
print(dog.last)

dog.last = 333
print(dog.stack)

del dog.last
print(dog.stack)
None
['one', 'two', 'three', 'four']
four
['one', 'two', 'three', 333]
['one', 'two', 'three']

Use case: arbitrary decoration

Note the use of functools.wraps to enable fully transparent decoration. In particular, reflection on the function being decorated will display the underlying function’s properties, not the decorator’s. Also, the docstring of the underlying function will be displayed by help. See functools.lru_cache for an extension.

import functools

def printargs(func):
    @functools.wraps(func)
    def decorated(*args, **kwargs):
        message = "Function `{0}' called with args: {1}, kwargs: {2}"
        print(message.format(func.__name__, args, kwargs))
        return func(*args, **kwargs)
    return decorated

@printargs
def fibonacci(n, memo = {0: 1, 1: 1}):
    """Return the nth fibonacci number."""
    if n < 0: return None
    if n not in memo:
        memo[n] = fibonacci(n - 1, memo) + fibonacci(n - 2, memo)
    return memo[n]

print(fibonacci) # use functools.wraps for transparent decoration
print('---------')
help(fibonacci)  # use functools.wraps for transparent decoration
print('---------')
print(fibonacci(8))
<function fibonacci at 0x10f205bf8>
---------
Help on function fibonacci in module __main__:

fibonacci(n, memo={0: 1, 1: 1})
    Return the nth fibonacci number.

---------
Function `fibonacci' called with args: (8,), kwargs: {}
Function `fibonacci' called with args: (7, {0: 1, 1: 1}), kwargs: {}
Function `fibonacci' called with args: (6, {0: 1, 1: 1}), kwargs: {}
Function `fibonacci' called with args: (5, {0: 1, 1: 1}), kwargs: {}
Function `fibonacci' called with args: (4, {0: 1, 1: 1}), kwargs: {}
Function `fibonacci' called with args: (3, {0: 1, 1: 1}), kwargs: {}
Function `fibonacci' called with args: (2, {0: 1, 1: 1}), kwargs: {}
Function `fibonacci' called with args: (1, {0: 1, 1: 1}), kwargs: {}
Function `fibonacci' called with args: (0, {0: 1, 1: 1}), kwargs: {}
Function `fibonacci' called with args: (1, {0: 1, 1: 1, 2: 2}), kwargs: {}
Function `fibonacci' called with args: (2, {0: 1, 1: 1, 2: 2, 3: 3}), kwargs: {}
Function `fibonacci' called with args: (3, {0: 1, 1: 1, 2: 2, 3: 3, 4: 5}), kwargs: {}
Function `fibonacci' called with args: (4, {0: 1, 1: 1, 2: 2, 3: 3, 4: 5, 5: 8}), kwargs: {}
Function `fibonacci' called with args: (5, {0: 1, 1: 1, 2: 2, 3: 3, 4: 5, 5: 8, 6: 13}), kwargs: {}
Function `fibonacci' called with args: (6, {0: 1, 1: 1, 2: 2, 3: 3, 4: 5, 5: 8, 6: 13, 7: 21}), kwargs: {}
34

Math

Constants

import math
print(math.pi)
print(math.e)
print(math.nan)
print(math.inf)
print(-math.inf)
3.141592653589793
2.718281828459045
nan
inf
-inf

Trigonometry

import math
# sin: opp / hyp
# cos: adj / hyp
# tan: opp / adj = sin/cos
# (pi / 4): 45 degs
print(math.cos(math.pi / 4))
print(math.sin(math.pi / 4))
print(math.tan(math.pi / 4))
0.7071067811865476
0.7071067811865475
0.9999999999999999
import math
print(math.radians(180) == math.pi)
print(math.radians(360) == 2 * math.pi)
print(math.degrees(2 * math.pi))
True
True
360.0

Floor and ceiling

import math
print(math.ceil(10.10))
print(math.floor(10.10))
print(round(10.10))
print(round(10.50))
print(round(10.51))
11
10
10
10
11

Factorial, sqrt, gcd

import math
print(math.factorial(3))
print(math.sqrt(64))
print(math.gcd(52, 8))
6
8.0
4

Random

randrange, choice, sample, shuffle

import random
print(random.random())
0.6601087654116915
import random
decider = random.randrange(2)
print('heads' if decider == 0 else 'tails')
tails
import random
# rand int 1 through 6
print(random.randrange(1, 7))
5
import random
winners = random.sample(range(1, 101), 5)
print(winners)
[92, 94, 68, 39, 46]
import random
possibilities = ['cat', 'dog', 'fish']
print(random.choice(possibilities))
cat
import random
possibilities = ['cat', 'dog', 'fish']
random.shuffle(possibilities)
print(possibilities)
['dog', 'cat', 'fish']

randrange, randint

import random as rand

# shuffle
rands = list(range(15))
rand.shuffle(rands)
print(rands)

# randrange:
print([rand.randrange(1, 10 + 1) for x in range(15)])

# alias for the above:
print([rand.randint(1, 10) for x in range(15)])
[10, 13, 2, 1, 4, 0, 9, 14, 12, 5, 7, 3, 6, 8, 11]
[10, 2, 6, 6, 8, 1, 5, 4, 7, 9, 7, 7, 6, 9, 5]
[1, 4, 3, 10, 3, 9, 5, 6, 5, 2, 7, 9, 7, 4, 9]

Statistics

Measures of central tendency: Mean, median, mode

import random
import statistics as stats
ages_data = [random.randrange(100) for x in list(range(10))]

print(ages_data)
print(stats.mean(ages_data))
print(stats.median(ages_data))

# when no mode, exception is thrown:
# statistics.StatisticsError: no unique mode; found 3 equally common values
try:
    print(stats.mode(ages_data))
except stats.StatisticsError:
    print('no mode')
[91, 51, 17, 9, 39, 25, 70, 68, 92, 15]
47.7
45.0
no mode

Measures of spread: variance, stdev

import statistics as stats
ages_data = [10, 13, 14, 12, 11, 10, 11, 10, 15]
print(stats.variance(ages_data))
print(stats.stdev(ages_data))
3.4444444444444446
1.855921454276674

More Collections

defaultdict

Use case: counting

from collections import defaultdict

strs = ['one', 'one', 'two', 'three']

counts = defaultdict(int)
for str in strs:
    counts[str] += 1

print(counts)
print(counts['one'])
print(counts.get('two'))
print(dict(counts))
defaultdict(<class 'int'>, {'one': 2, 'two': 1, 'three': 1})
2
1
{'one': 2, 'two': 1, 'three': 1}

Use case: Nested counting

from collections import defaultdict

strs = ['one two', 'one two', 'two one', 'three four']

counts = defaultdict(lambda: defaultdict(int))
for str in strs:
    lead, trail = str.split()
    counts[lead][trail] += 1

print(counts['one']['two'])
print(counts['two']['one'])
print(counts['three']['four'])
print({ k: dict(v) for k, v in counts.items() })
2
1
1
{'one': {'two': 2}, 'two': {'one': 1}, 'three': {'four': 1}}

namedtuple

from collections import namedtuple

records = [
    '1896\tThomas Burke\tUSA\t100m men\n',
    '1896\tThomas Burke\tRUS\t100m men\n',
    '1896\tThomas Burke\tUSA\t100m men\n',
    '1896\tThomas Burke\tUSA\t100m men\n',
    '1896\tThomas Burke\tFRA\t100m men\n',
    '1896\tThomas Burke\tRUS\t100m men\n',
    '1896\tThomas Burke\tCAD\t100m men\n',
    '1896\tThomas Burke\tFRA\t100m men\n',
    '1896\tThomas Burke\tCAD\t100m men\n',
    '1896\tThomas Burke\tFRA\t100m men\n',
    '1896\tThomas Burke\tFRA\t100m men\n'
]

Medal = namedtuple('medal', 'year athlete country event')
medal1 = Medal(*records[0].rstrip().split('\t'))

print()
print(medal1)
print(medal1.year)
print(medal1.athlete)
Python 3.6.1 |Continuum Analytics, Inc.| (default, May 11 2017, 13:04:09)
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.57)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> >>> ... ... ... ... ... ... ... ... ... ... ... ... >>> >>> >>> >>> >>>
medal(year='1896', athlete='Thomas Burke', country='USA', event='100m men')
1896
Thomas Burke

Counter

An alternative to using defaultdict~s. Provides conveniences like ~most_common.

from collections import Counter

medals = [Medal(*record.rstrip().split('\t')) for record in records]
teams = Counter(medal.country for medal in medals)

print()
print(teams.most_common(3))
print(teams.get('USA'))
print(teams.get('RUS'))

>>> >>> >>> >>>
[('FRA', 4), ('USA', 3), ('RUS', 2)]
3
2

Iteration tools: itertools

Infinite counting: count

import itertools as it

# runs infinitely
for x in it.count(start=10, step=5):
    print(x)

    if x == 30:
        break
10
15
20
25
30

Infinite cycling: cycle

import itertools as it
x = 0
for c in it.cycle("RACECAR"):
    print(c)
    x += 1
    if x > 10:
        break
R
A
C
E
C
A
R
R
A
C
E

Infinite repeating: repeat

import itertools as it
x = 0

for r in it.repeat(True):
    print(r)
    x += 1
    if x > 4:
        break
True
True
True
True
True

Permutations: permutations

import itertools as it
election = {1: 'Barb', 2: 'Karen', 3: 'Erin'}
   for p in it.permutations(election):
       print(p)
(1, 2, 3)
(1, 3, 2)
(2, 1, 3)
(2, 3, 1)
(3, 1, 2)
(3, 2, 1)
import itertools as it
election = {1: 'Barb', 2: 'Karen', 3: 'Erin'}
for p in it.permutations(election.values()):
    print(p)
('Barb', 'Karen', 'Erin')
('Barb', 'Erin', 'Karen')
('Karen', 'Barb', 'Erin')
('Karen', 'Erin', 'Barb')
('Erin', 'Barb', 'Karen')
('Erin', 'Karen', 'Barb')

Combinations: combinations

import itertools as it
election = {1: 'Barb', 2: 'Karen', 3: 'Erin'}
for p in it.combinations(election, r=2):
    print(p)
(1, 2)
(1, 3)
(2, 3)
import itertools as it
election = {1: 'Barb', 2: 'Karen', 3: 'Erin'}
for p in it.combinations(election.values(), r=2):
    print(p)
('Barb', 'Karen')
('Barb', 'Erin')
('Karen', 'Erin')

starmap: map a function over a list of arg lists

from itertools import starmap

sums = starmap(lambda x, y: x + y, [(1, 2), (3, 5), (8, 8)])
sums1 = map(lambda x: x[0] + x[1], [(1, 2), (3, 5), (8, 8)])

print(list(sums))
print(list(sums1))
[3, 8, 16]
[3, 8, 16]

Convolution (exhaustive): zip_longest

from itertools import zip_longest

tuples = zip_longest(range(2), range(3), range(5))
print(list(tuples))

tuples = zip_longest(range(2), range(3), range(5), fillvalue=0)
print(list(tuples))
[(0, 0, 0), (1, 1, 1), (None, 2, 2), (None, None, 3), (None, None, 4)]
[(0, 0, 0), (1, 1, 1), (0, 2, 2), (0, 0, 3), (0, 0, 4)]

Functional tools: functools

functools.wraps

See Use case: arbitrary decoration

functools.lru_cache

Provides memoization via a decorator.

from functools import lru_cache
from functools import wraps

def printargs(func):
    @wraps(func)
    def decorated(*args, **kwargs):
        message = "Function `{0}' called with args: {1}, kwargs: {2}"
        print(message.format(func.__name__, args, kwargs))
        return func(*args, **kwargs)
    return decorated

@lru_cache(maxsize=None)
@printargs
def fibonacci(n):
    """Return the nth fibonacci number."""
    if n < 0: return None
    if n < 2: return 1
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(8))
print(fibonacci.cache_info())
Function `fibonacci' called with args: (8,), kwargs: {}
Function `fibonacci' called with args: (7,), kwargs: {}
Function `fibonacci' called with args: (6,), kwargs: {}
Function `fibonacci' called with args: (5,), kwargs: {}
Function `fibonacci' called with args: (4,), kwargs: {}
Function `fibonacci' called with args: (3,), kwargs: {}
Function `fibonacci' called with args: (2,), kwargs: {}
Function `fibonacci' called with args: (1,), kwargs: {}
Function `fibonacci' called with args: (0,), kwargs: {}
34
CacheInfo(hits=6, misses=9, maxsize=None, currsize=9)

functools.partial

Partial application.

from functools import partial

def nums_list(lower, upper, predicate=lambda x: True):
    return [n for n in range(lower, upper + 1) if predicate(n)]

print(nums_list(1, 10))

even_nums_list = partial(nums_list, predicate=lambda x: x % 2 is 0)
print(even_nums_list(1, 10))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[2, 4, 6, 8, 10]

Date/Time, Calendars, Timers

Current time, formatting dates and times

from datetime import datetime

now = datetime.now()
print(now.date())
print(now.time())
print('%02d:%02d:%02d' % (now.hour, now.minute, now.second))

print(now.strftime('%A, %b %d, %Y at %I:%M:%S %p'))
print(now.strftime('%D'))
2017-09-20
11:44:27.997500
11:44:27
Wednesday, Sep 20, 2017 at 11:44:27 AM
09/20/17
import datetime as dt
now = dt.datetime.now()
print(now)
print(now.year)
print(now.hour)
2017-09-20 11:44:28.249418
2017
11

Calculating future times

from datetime import datetime, timedelta

now = datetime.now()
print(now.date())

two_days_from_now = now + timedelta(days=2)
print(two_days_from_now.date())

three_weeks_ago = now - timedelta(weeks=3)
print(three_weeks_ago.date())

if two_days_from_now > now:
    two_days_ago = now - (two_days_from_now - now)
    print(two_days_ago.date())
2017-09-20
2017-09-22
2017-08-30
2017-09-18

Calendars

import calendar
cal = calendar.month(2001, 10)
print(cal)
    October 2001
Mo Tu We Th Fr Sa Su
 1  2  3  4  5  6  7
 8  9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
import calendar
cal = calendar.weekday(2001, 10, 11)
print(cal) # returns the zero-indexed day of week
3
import calendar
print(calendar.isleap(2004))
print(calendar.isleap(1996))
print(calendar.isleap(1995))
True
True
False

Creating timers

import time

try:
    run = input('Start? > ')
except:
    run = 'yes'

seconds = 0
if run == 'yes':
    while seconds < 5:
        print('> ', seconds)
        time.sleep(1)
        seconds += 1
Start? > >  0
>  1
>  2
>  3
>  4

Text munging

HTML Parsing: html.parser

from html.parser import HTMLParser

class HTMLParser(HTMLParser):
    def handle_starttag(self, tag, attrs):
        print('Start tag: ', tag)
        for attr in attrs:
            print('attr: ', attr)

    def handle_endtag(self, tag):
        print('End tag: ', tag)

    def handle_comment(self, data):
        print('Comment: ', data)

    def handle_data(self, data):
        data = data.rstrip()
        if len(data) > 0:
            print('Data: ', data)

parser = HTMLParser()
parser.feed("""
<html>
    <head>
    <title>A web page</title>
    </head>
    <body>
    <!-- this is a comment -->
    <h1>this is a header</h1>
    </body>
</html>
""")

print()
Start tag:  html
Start tag:  head
Start tag:  title
Data:  A web page
End tag:  title
End tag:  head
Start tag:  body
Comment:   this is a comment
Start tag:  h1
Data:  this is a header
End tag:  h1
End tag:  body
End tag:  html

Parsing html from a saved file:

html_file = open('sample-html.html', 'r')
html_str = ""
for line in html_file:
    s += line

parser.feed(s)

Text wrapping: textwrap

import textwrap as tw
hymn = """
          From the halls of Montezuma to the shores of Tripoli, we fight our country's battles, in the air on land and sea. First to fight for right and freedom, and to keep our honor clean, we our proud to claim the title of United States Marine.
   """

# no dedent, just wrap (default width is 70)
print('Without dedent')
print('--------------')
print(tw.fill(hymn))

# with dedent
print('\nDedent')
print('------')
print(tw.dedent(hymn).strip())

# with fill and width arg (includes dedent)
print('\nFill to width')
print('---------------')
print(tw.fill(hymn, width=60).strip())

print('\nControlling indent')
print('------------------')
print(tw.fill(hymn, initial_indent='  ', subsequent_indent='   '))

print('\nShortening text')
print('----------------')
print(tw.shorten(hymn, width=50, placeholder='...'))
Without dedent
--------------
        From the halls of Montezuma to the shores of Tripoli, we fight
our country's battles, in the air on land and sea. First to fight for
right and freedom, and to keep our honor clean, we our proud to claim
the title of United States Marine.

Dedent
------
From the halls of Montezuma to the shores of Tripoli, we fight our country's battles, in the air on land and sea. First to fight for right and freedom, and to keep our honor clean, we our proud to claim the title of United States Marine.

Fill to width
---------------
From the halls of Montezuma to the shores of
Tripoli, we fight our country's battles, in the air on land
and sea. First to fight for right and freedom, and to keep
our honor clean, we our proud to claim the title of United
States Marine.

Controlling indent
------------------
          From the halls of Montezuma to the shores of Tripoli, we
   fight our country's battles, in the air on land and sea. First to
   fight for right and freedom, and to keep our honor clean, we our
   proud to claim the title of United States Marine.

Shortening text
----------------
From the halls of Montezuma to the shores of...

HTTP: urllib.request, json

HTTP, urllib, JSON

import urllib.request as req
import json
import textwrap as tw
from pprint import PrettyPrinter

pp = PrettyPrinter(depth=3)
url = 'https://www.googleapis.com/books/v1/volumes?q=isbn:1101904224'

with req.urlopen(url) as f:
    json_string = f.read().decode('utf-8')
    dict = json.loads(json_string)
    pp.pprint(dict)
{'items': [{'accessInfo': {...},
            'etag': '/FXgwbeW4Mk',
            'id': '1imJDAAAQBAJ',
            'kind': 'books#volume',
            'saleInfo': {...},
            'searchInfo': {...},
            'selfLink': 'https://www.googleapis.com/books/v1/volumes/1imJDAAAQBAJ',
            'volumeInfo': {...}}],
 'kind': 'books#volumes',
 'totalItems': 1}

Batteries-included web-scraping: requests and bs4

import requests

URL = 'https://jsonplaceholder.typicode.com/posts/1'
resp = requests.get(URL)
print(resp.status_code)
print(resp.text)
print(resp.json())
200
{
  "userId": 1,
  "id": 1,
  "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
  "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
}
{'userId': 1, 'id': 1, 'title': 'sunt aut facere repellat provident occaecati excepturi optio reprehenderit', 'body': 'quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto'}
from bs4 import BeautifulSoup
import requests

URL = 'https://jsonplaceholder.typicode.com'
resp = requests.get(URL)
print('------ status code ------')
print(resp.status_code)
html = BeautifulSoup(resp.text, 'html.parser')
print('------ title ------')
print(html.title)
print(html.title.name)
print(html.title.string)
print('------ p ------')
print(html.p)
print('------ all <a> ------')
print(html.find_all('a'))
print('------ all text ------')
print(html.get_text())
------ status code ------
200
------ title ------
<title>JSONPlaceholder - Fake online REST API for developers</title>
title
JSONPlaceholder - Fake online REST API for developers
------ p ------
<p>
Fake Online REST API for Testing and Prototyping<br/>
<small>
powered by <a href="https://github.com/typicode/json-server">JSON Server</a>
and <a href="https://github.com/typicode/lowdb">lowdb</a>
</small>
</p>
------ all <a> ------
[<a href="https://github.com/typicode/json-server">JSON Server</a>, <a href="https://github.com/typicode/lowdb">lowdb</a>, <a class="twitter-follow-button" data-show-count="false" href="https://twitter.com/typicode">
Follow @typicode
</a>, <a href="/posts">/posts</a>, <a href="/comments">/comments</a>, <a href="/albums">/albums</a>, <a href="/photos">/photos</a>, <a href="/todos">/todos</a>, <a href="/users">/users</a>, <a href="https://github.com/typicode/jsonplaceholder#how-to">examples</a>, <a href="/posts">/posts</a>, <a href="/posts/1">/posts/1</a>, <a href="/posts/1/comments">/posts/1/comments</a>, <a href="/comments?postId=1">/comments?postId=1</a>, <a href="/posts?userId=1">/posts?userId=1</a>, <a href="https://github.com/typicode/json-server">JSON Server</a>, <a href="https://my-json-server.typicode.com">My JSON Server</a>, <a href="https://github.com/typicode">typicode</a>, <a href="https://github.com/typicode/jsonplaceholder">GitHub</a>, <a href="https://patreon.com/typicode" onclick="trackOutboundLink('https://patreon.com/typicode')">Patreon page</a>, <a class="more" href="https://github.com/typicode/hotel">
<p>
Hotel - 🏩 the friendly process manager for developers
</p>
</a>]
------ all text ------




JSONPlaceholder - Fake online REST API for developers





hljs.initHighlightingOnLoad();






JSONPlaceholder

Fake Online REST API for Testing and Prototyping

powered by JSON Server
and lowdb







(function(d, s, id) {
            var js, fjs = d.getElementsByTagName(s)[0];
            if (d.getElementById(id)) return;
            js = d.createElement(s); js.id = id;
            js.src = "//connect.facebook.net/en_US/sdk.js#xfbml=1&version=v2.0";
            fjs.parentNode.insertBefore(js, fjs);
          }(document, 'script', 'facebook-jssdk'));




Follow @typicode



Intro

JSONPlaceholder is a free online REST service that you can use whenever you need some fake data.


It's great for tutorials, faking a server, sharing code examples, ...

Example
Run this code in a console or from anywhere (CORS and JSONP supported).
var root = 'http://jsonplaceholder.typicode.com';

$.ajax({
  url: root + '/posts/1',
  method: 'GET'
}).then(function(data) {
  console.log(data);
});

Run

"Try me!"
Resources

Inspired by common use cases.


/posts100 items
/comments500 items
/albums100 items
/photos5000 items
/todos200 items
/users10 items

Routes

All HTTP verbs are supported.
View usage examples.


GET/posts
GET/posts/1
GET/posts/1/comments
GET/comments?postId=1
GET/posts?userId=1
POST/posts
PUT/posts/1
PATCH/posts/1
DELETE/posts/1


Use your OWN data

JSON Server powers this website.
You can use it to create the same fake API in less than 30 seconds with your own data.

npm install -g json-server
Or you can try My JSON Server free service.



Coded and built with
by typicode.
Source code available on
GitHub.
Patreon page.




Hotel - 🏩 the friendly process manager for developers



      (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
      (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
      m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
      })(window,document,'script','//www.google-analytics.com/analytics.js','ga');

      ga('create', 'UA-44497010-1', 'typicode.com');
      ga('send', 'pageview');

      var trackOutboundLink = function(url) {
        ga('send', 'event', 'outbound', 'click', url, {
          'transport': 'beacon'
        });
      }

window.twttr = (function(d, s, id) {
      var js, fjs = d.getElementsByTagName(s)[0],
        t = window.twttr || {};
      if (d.getElementById(id)) return t;
      js = d.createElement(s);
      js.id = id;
      js.src = "https://platform.twitter.com/widgets.js";
      fjs.parentNode.insertBefore(js, fjs);

      t._e = [];
      t.ready = function(f) {
        t._e.push(f);
      };

  return t;
    }(document, "script", "twitter-wjs"));


      // Use http or https based on location.protocol
      var exampleText = $('#example').text()
      $('#example').text(exampleText.replace('http:', location.protocol))

      // Highlight result element
      $('#result').each(function(i, block) {
        hljs.highlightBlock(block);
      });

      // Run example
      $('#run').click(function() {
        var root = location.protocol + '//jsonplaceholder.typicode.com';
        $.ajax({
          url: root + '/posts/1',
          method: 'GET'
        }).then(function(data) {
          var str = JSON.stringify(data, null, '\t')
          $('#result').html(
            str.replace(/\n/g, '<br/>')
               .replace(/\\n/g, ' ')
               .replace(/\t/g, '&nbsp;&nbsp;')
          );

          $('#result').each(function(i, block) {
            hljs.highlightBlock(block);
          });
        });
      });

      // Tell that jQuery can be used in console
      console.log('You can use jQuery functions')

Databases: sqlite3

import sqlite3

# create db file, connect to db
dbfile = 'test.db'
db = sqlite3.connect(dbfile)

# Optional: set row factory. Default behavior is to return tuples.
# Using a row factory makes it easy to convert the row object to a dictionary
# by passing a row to the dict initializer (see cursor logic below)
db.row_factory = sqlite3.Row

# SQL statements
db.execute('drop table if exists messages;')
db.execute('create table messages (id int, body text);')
insert_statement = 'insert into messages (id, body) values (?, ?);'
db.execute(insert_statement, (1, 'first!'))
db.execute(insert_statement, (2, 'this is a second message'))
db.commit()

cursor = db.execute('select * from messages order by id;')
for row in cursor:
row_dict = dict(row)
    print(row_dict)

import os
os.remove(dbfile)
{'id': 1, 'body': 'first!'}
{'id': 2, 'body': 'this is a second message'}

Testing: unittest

class Speaker():
    def sayHi(self):
        return 'hi'

# import speaker
import unittest

class TestSpeaker(unittest.TestCase):
    def setUp(self):
        return None

    def testSayHi(self):
        speaker = Speaker()
        greeting = speaker.sayHi()
        self.assertEqual(greeting, 'hi')

  # the following is just for displaying results here:
  from unittest import TextTestRunner, TestLoader
  from io import StringIO
  output = StringIO()
  test_suite = TestLoader().loadTestsFromTestCase(TestSpeaker)
  test_result = TextTestRunner(output).run(test_suite)
  print(output.getvalue())
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

Web development: flask

Packages and utilities

  • Flask
  • werkzeug (WSGI utilities)
  • jinja2 (template engine)
  • Bootswatch (bootstrap themes)
  • psycopg2: pip install psycopg2
  • Flask extensions:
    • SQLAlchemy: pip install flask-sqlalchemy
    • Flask-WTF: pip install flask-wtf

Routes / Controller

routes.py:

from flask import Flask, redirect, render_template, session, url_for

from forms import LoginForm, SignupForm
from models import User, db

app = Flask(__name__)
app.config['SQLALCHEMY_DB_URI'] = 'postgresql://localhost/dbnamehere'
app.secret_key = 'dev-key'
db.init_app(app)


@app.route('/')
def index():
return render_template('index.html')


@app.route('/signup', methods=['GET', 'POST'])
def signup():
if 'email' in session:
return redirect(url_for('home'))

    form = SignupForm()
    if request.method == 'GET':
    return render_template('signup.html', form=form)

    elif request.method == 'POST':
        if form.validate():
            newuser = User(form.first_name.data, form.last_name.data,
                        form.email.data, form.password.data)
            db.session.add(newuser)
            db.session.commit()

            # create cookie for current session
            session['email'] = newuser.email
        return redirect(url_for('home'))
        else:
        return render_template('signup.html', form=form)

    @app.route('/login', methods=['GET', 'POST'])
    def login():
        if 'email' in session:
        return redirect(url_for('home'))

        form = LoginForm()
        if request.method == 'GET':
        return render_template('login.html')

        if request.method == 'POST':
            if not form.validate():
            return render_template('login.html', form=form)
            user = User.query.filter_by(email=form.email.data).first()
            if user is None or user.check_password(form.password.data):
            return render_template('login.html', form=form)
            session['email'] = user.email
        return redirect(url_for('home'))

    @app.route('/logout', methods=['GET'])
    def logout():
        session.pop('email', None)
    return redirect(url_for('index'))

    @app.route('/home', methods=['GET'])
    def home():
        if 'email' in session:
        return render_template('home.html')
        else:
        return redirect(url_for('login'))


if __name__ == '__main__':
    app.run(debug=True)

Models

models.py:

from flask.ext.sqlalchemy import SQLAlchemy
from werkzeug import check_password_hash, generate_password_hash

db = SQLAlchemy()


class User(db.Model):
    __tablename__ = 'users'
    uid = db.Column(db.Integer, primary_key=True)
    firstname = db.Column(db.String(100))
    lastname = db.Column(db.String(100))
    email = db.Column(db.String(120), unique=True)
    pwdhash = db.Column(db.String(54))

    def __init__(self, firstname, lastname, email, password):
        self.firstname = firstname
        self.lastname = lastname
        self.email = email
        self.set_password(password)

    def set_password(self, password):
        self.pwdhash = generate_password_hash(password)

    def check_password(self, password):
        return check_password_hash(self.pwdhash, password)

Forms

from flask_wtf import Form
from wtforms import PasswordField, StringField, SubmitField
from wtforms.validators import DataRequired, Email, Length


class SignupFrom(Form):
    first_name = StringField(
        'First name',
        validators=[DataRequired('Please enter your first name.')])

    last_name = StringField(
        'Last name', validators=[DataRequired('Please enter your last name.')])

    email = StringField(
        'Email',
        validators=[
            DataRequired('Please enter a valid email address.'),
            Email('Please enter a valid email address.')
        ])

    password = PasswordField(
        'Password',
        validators=[
            DataRequired('Please enter a password.'),
            Length(
                min=6, message='Password must be at least 6 characters long.')
        ])

    submit = SubmitField('Sign up')


class LoginForm(Form):
    email = StringField(
        'Email',
        validators=[
            DataRequired('Please enter a valid email address.'),
            Email('Please enter a valid email address.')
        ])

    password = PasswordField(
        'Password', validators=[DataRequired('Please enter a password.')])

    submit = SubmitField('Log in')

Geocoding and geoplotting: geopy and matplotlib

Geocoding

import geopy
from pprint import pprint

olympics_data = """
Athens (1896)
Paris (1900)
St Louis (1904)
London (1908)
Stockholm (1912)
Antwerp (1920)
Paris (1924)
Amsterdam (1928)
Los Angeles (1932)
Berlin (1936)
London (1948)
Helsinki (1952)
Melbourne / Stockholm (1956)
Rome (1960)
Tokyo (1964)
Mexico (1968)
"""

def location(entry):
    *city, year = entry.split()
    city = ' '.join(city)
    year = year.strip('()')
    return (city.split('/')[0], city, year)


geolocator = geopy.geocoders.Nominatim()
cities = {}

for entry in olympics_data.strip().split('\n'):
    city_name, full_city, _year = location(entry)
    cities[full_city] = geolocator.geocode(city_name)

pprint(cities)
{'Amsterdam': Location(Amsterdam, Centrum, Amsterdam, MRA, Stadsregio Amsterdam, Noord-Holland, Nederland, (52.3745403, 4.89797550561798, 0.0)),
 'Antwerp': Location(Antwerpen, Vlaanderen, België - Belgique - Belgien, (51.2211097, 4.3997081, 0.0)),
 'Athens': Location(Athens, Athens - Clarke County, Georgia, United States of America, (33.9550905, -83.3881868, 0.0)),
 'Berlin': Location(Berlin, Deutschland, (52.5170365, 13.3888599, 0.0)),
 'Helsinki': Location(Helsinki, Helsingin seutukunta, Uusimaa, Etelä-Suomi, Suomi, (60.1674086, 24.9425683, 0.0)),
 'London': Location(London, Greater London, England, UK, (51.5073219, -0.1276474, 0.0)),
 'Los Angeles': Location(LA, Los Angeles County, California, United States of America, (34.054935, -118.244476, 0.0)),
 'Melbourne / Stockholm': Location(Melbourne, City of Melbourne, Greater Melbourne, Victoria, 3000, Australia, (-37.8142176, 144.9631608, 0.0)),
 'Mexico': Location(Ciudad de México, Cuauhtémoc, México, (19.4326009, -99.1333416, 0.0)),
 'Paris': Location(Paris, Île-de-France, France métropolitaine, 75000;75001;75002;75003;75004;75005;75006;75007;75008;75009;75010;75011;75012;75013;75014;75015;75016;75017;75018;75019;75020;75116, France, (48.8566101, 2.3514992, 0.0)),
 'Rome': Location(Roma, RM, LAZ, Italia, (41.8933203, 12.4829321, 0.0)),
 'St Louis': Location(St. Louis, City of St. Louis, Missouri, United States of America, (38.6272733, -90.1978889, 0.0)),
 'Stockholm': Location(Sthlm, Stockholm, Landskapet Uppland, Stockholms län, Svealand, Sverige, (59.3251172, 18.0710935, 0.0)),
 'Tokyo': Location(東京都, 日本, (34.2255804, 139.294774527387, 0.0))}

Plotting

import matplotlib.pyplot as pp
from mpl_toolkits.basemap import Basemap

pp.figure(figsize=(10, 5))
world = Basemap()
world.drawcoastlines(linewidth=0.25)
world.drawcountries(linewidth=0.25)

for city, posn in cities.items():
    world.plot(posn.longitude, posn.latitude, 'r.', markersize=10, latlon=True)

More libraries

  • Pillow: manipulate images
  • matplotlib: make plots

Classes

Enums

from enum import Enum

class Fruit(Enum):
    APPLE = 1
    BANANA = 2
    MANGO = 3
    PINEAPPLE = 4
    GUAVA = 5

print(Fruit.APPLE)
print(Fruit.APPLE == Fruit.APPLE)
print(Fruit.APPLE == 1)
class Fruit:
    APPLE = 1
    BANANA = 2
    MANGO = 3
    PINEAPPLE = 4
    GUAVA = 5

print(Fruit.APPLE)
print(Fruit.APPLE == Fruit.APPLE)
print(Fruit.APPLE == 1)

Can enforce uniquness

from enum import Enum

class Fruit(Enum):
    APPLE = 1
    ORANGE = 1

print(Fruit.APPLE)
print(Fruit.ORANGE)
from enum import Enum, unique

try:
    @unique
    class Fruit(Enum):
        APPLE = 1
        ORANGE = 1
except ValueError as e:
    print(e)

Can assign values automatically

from enum import Enum, auto

class Fruit(Enum):
    APPLE = auto()
    ORANGE = auto()

print(Fruit.APPLE.value)
print(Fruit.ORANGE.value)

Class string functions

str, format, repr, bytes

String functionCalled when x is called
obj.__str__(self)str(obj), print(obj), "{obj}".format(obj)
obj.__repr__(self)repr(obj)
obj.format(self, format_spec)format(obj, format_spec)
obj.__bytes__(self)bytes(obj)
class Person:
    def __init__(self, fname, lname, age):
        self.fname = fname
        self.lname = lname
        self.age = age

    def __repr__(self):
        return f'Person({self.fname}, {self.lname}, {self.age})'

    def __str__(self):
        return f'<Person: {self.fname} {self.lname}>'


person = Person('Joe', 'Marini', 25)

# with neither __repr__ or __str__ defined:
#   <__main__.Person object at 0x1013e16a0>

print(repr(person))  # Rule of thumb: ~repr~ should be copy-pastable to create a new record
print(str(person))   # `print` calls `str`, which falls back to `repr` if undefined

Computed attributes

from datetime import datetime as dt

class Dog:
    def __init__(self, vaccination_year=None):
        self.vaccination_date = dt.strptime(str(vaccination_year), '%Y') if vaccination_year else None

    @property
    def has_been_vaccinated(self):
        return self.vaccination_date and self.vaccination_date <= dt.now()


dog = Dog(vaccination_year=2018)
print(dog.has_been_vaccinated)

Custom class numerical operators

class Molecule:
    def __init__(self, *elements):
        self.elements = list(elements)

    def __add__(self, other):
        for e in other.elements:
            self.elements.append(e)
        return self

    def __str__(self):
        return str(self.elements)



class Atom:
    def __init__(self, symbol):
        self.symbol = symbol
        self.elements = [self]

    def __add__(self, other):
        molecule = Molecule()
        for e in other.elements:
            molecule = molecule + e
        return molecule

    def __mul__(self, factor):
        molecule = Molecule()
        for _ in range(factor):
            molecule = molecule + self
        return molecule

    def __repr__(self):
        return self.symbol


h2 = Atom('H') * 2
print(h2)

o = Atom('O')
print(o)

water = h2 + o
print(water)

Logging

Logging levels

Message levelsLogging APIDescription
DEBUGlogging.debug()Diagnostic
INFOlogging.info()General
WARNINGlogging.warning()Something unexpected, or an approaching problem
ERRORlogging.error()Unable to perform a specific operation
CRITICALlogging.critical()Program may not be able to continue

Initializing

import logging

fmtstr = "%(asctime)s: %(levelname)s: %(funcName)s Line:%(lineno)d %(message)s"
datestr = "%m/%d/%Y %I:%M %M"

logging.basicConfig(
    level=logging.DEBUG,
    filename='output.log',
    filemode='w',
    format=fmtstr,
    datefmt=datestr)

logging.error("This is an info-level log message")

Generators

Refresher

Generators maintain state and provide lazy sequences / sequential evaluation of functions. Generator functions return generator objects (an iterator).

Generator expression / generator comprehension

nums = [7, 22, 4.5, 99.7, '6', '5']
ints = (int(i) for i in nums)
print([i for i in ints if i % 2 is 0])
nums = [7, 22, 4.5, 99.7, '6', '5']
evens = (int(i) for i in nums if int(i) % 2 is 0)
print(evens.__next__())
print(evens.__next__())
print(evens.__next__())

try:
    print(evens.__next__())
except StopIteration as err:
    print("StopIteration", err)

Generator function

def even_integers(lower, upper):
    for i in range(lower, upper + 1):
        if i % 2 is 0:
            yield i

evens = even_integers(3, 23)
print(evens.__next__())
print(evens.__next__())
print(evens.__next__())

Generator pipelines

def open(filename):
    return """
        Tammie Sugrue
        Roselyn Burr
        Marybeth Candelario
        Mathilda Wimer
        Berneice Dooley
        Ana Chou
        Valencia Harrold
        Shelby Lao
        Felton Healey
        Tammy Locklear
        Cedrick Kehoe
        Willene Clingman
        Norene Snedden
        Alaine Magruder
        Sid Letellier
        Corrie Cutlip
        Staci Carlow
        Elton Rakes
        Gwendolyn Mannion
        Emilie Primmer
        """

full_names = (name.strip() for name in open("names.txt").strip().split("\n"))
lengths = ((name, len(name)) for name in full_names)
longest = max(lengths, key=lambda x: x[1])

print(longest)

Context managers: with

Python object that’s able to act as a control structure after the with statement. Typically used to handle setup and teardown logic.

The basic form:

from contextlib import contextmanager

@contextmanager
def simple_cm(*args):
    try:
        # setup code
        yield
    finally:
        # teardown code

Coroutines

Properties

  • Built from a generator, but conceptually different
  • Repeatedly receives input
  • Processes input
  • Stops at a yield statement
  • Unlike a function, has persistent properties that can be changed
  • yield not only pauses flow, also captures sent values

Minimal Example

def coroutine():
    while True:
        x = yield
        print(x)
try:
    cor = coroutine()
    cor.send(48)
except TypeError as err:
    print("can't send non-None value to a just started generator")

cor.__next__() # start generator
cor.send(48)
cor.send(55)

Threading

Minimal Example: multiprocessing.dummy

multiprocessing.dummy replicates the API of multiprocessing but is just a wrapper around the threading module.

Multi-threaded

Runtime: 0.5225861072540283

import requests
from time import time
from multiprocessing.dummy import Pool as ThreadPool
from pprint import pprint

urls = [
    'http://www.python.org',
    'http://www.python.org/about/',
    'http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html',
    'http://www.python.org/doc/',
    'http://www.python.org/download/',
    'http://www.python.org/getit/',
    'http://www.python.org/community/',
    'https://wiki.python.org/moin/',
]

start_time = time()
thread_pool = ThreadPool(len(urls))
results = thread_pool.map(requests.get, urls)
thread_pool.close()
thread_pool.join()

pprint(results)
pprint("runtime: {}".format(time() - start_time))

Comparison to single-threaded implementation

Runtime: 1.4841949939727783

import requests
from time import time
from pprint import pprint

      urls = [
          'http://www.python.org',
          'http://www.python.org/about/',
          'http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html',
          'http://www.python.org/doc/',
          'http://www.python.org/download/',
          'http://www.python.org/getit/',
          'http://www.python.org/community/',
          'https://wiki.python.org/moin/',
      ]

      start_time = time()
  results = map(requests.get, urls)
      pprint(list(results))
      pprint("runtime: {}".format(time() - start_time))

Multiprocessing

Minimal Example: Pool (preferred)

import requests
from time import time
from multiprocessing import Pool
from pprint import pprint

     urls = [
         'http://www.python.org',
         'http://www.python.org/about/',
         'http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html',
         'http://www.python.org/doc/',
         'http://www.python.org/download/',
         'http://www.python.org/getit/',
         'http://www.python.org/community/',
         'https://wiki.python.org/moin/',
     ]

     start_time = time()
     with Pool(len(urls)) as pool:
     results = pool.map(requests.get, urls)
         pprint(results)

     pprint("runtime: {}".format(time() - start_time))

Minimal Example: Process

import requests
from time import time
from multiprocessing import Process
from pprint import pprint

urls = [
    'http://www.python.org',
    'http://www.python.org/about/',
    'https://wiki.python.org/moin/',
]

start_time = time()
get_requests = [Process(target=requests.get, args=(url,)) for url in urls]
for get_request in get_requests:
    get_request.start()
    get_request.join()

pprint(get_requests)
pprint("runtime: {}".format(time() - start_time))