Skip to content

Instantly share code, notes, and snippets.

@zzl0
Last active February 6, 2019 17:05
Show Gist options
  • Save zzl0/f363d528075193e53c8498cec0404272 to your computer and use it in GitHub Desktop.
Save zzl0/f363d528075193e53c8498cec0404272 to your computer and use it in GitHub Desktop.

Python 201

In this document, we are going to be talking about some Python idioms.

  • high level design principles
  • design patterns
  • idioms
  • code style

Disclaimer

I am not an expert.

Python Philosophy

>>> import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!

Beyond PEP 8

https://www.python.org/dev/peps/pep-0008/

Outline

  • Data structures
  • Functions
  • Classes
  • Control flow
  • Performance
  • Elio
    • ABC
    • RegEx

Bool

False values:

False, None, 0, '', [], {}, set()

Number

42
10000000000000000000
12.34
1 + 2j

String

# I am a comment

"hello\n"
'hello\n'
r'hello\n'

'''
You can write
multiple lines
like this
'''
name = "PX"
message = "Welcome"
f'Hello {name}, {message}'

List

colors = ['red', 'green', 'blue', 'yellow']

print(colors[0])
print(colors[-1])
print(colors[0:3])
print(colors[:3])

for c in colors:
    print(c)

for i, c in enumerate(colors):
    print(f'The {i} color is {c}')

for c in reversed(colors):
    print(c)


names = ['raymond', 'rachel', 'matthew']
ages = [23, 32, 8]

for name, age in zip(names, ages):
    print(name, '---->', age)

print(sorted(names, key=len))

Tuple

  • immutable lists
  • records with no field names
name, age = ('rachel', 23)

# unpacking

>>> a, b, *rest = range(5)
>>> a, b, rest
(0, 1, [2, 3, 4])
>>> a, b, *rest = range(3)
>>> a, b, rest
(0, 1, [2])
>>> a, b, *rest = range(2)
>>> a, b, rest
(0, 1, [])
>>> a, *body, c, d = range(5)
>>> a, body, c, d
(0, [1, 2], 3, 4)
>>> *head, b, c, d = range(5)
>>> head, b, c, d
([0, 1], 2, 3, 4)

# named tuple

>>> from collections import namedtuple
>>> City = namedtuple('City', 'name country population coordinates')
>>> tokyo = City('Tokyo', 'JP', 36.933, (35.689722, 139.691667))
>>> tokyo
City(name='Tokyo', country='JP', population=36.933, coordinates=(35.689722, 139.691667))
>>> tokyo.population
36.933
>>> tokyo.coordinates
(35.689722, 139.691667)
>>> tokyo[1]
'JP'

# swap two variable

a, b = b, a

Dict

The dict type is not only widely used in our programs but also a fundamental part of the Python implementation. Module namespaces, class and instance attributes and function keyword arguments are some of the fundamental constructs where dictionaries are deployed. The built-in functions live in __builtins__.__dict__.

students = {
    "aaa": 1,
    "bbb": 2
}

squares = {x: x ** 2 for x in range(10)}


names = ['raymond', 'rachel', 'matthew']
ages = [23, 32, 8]
d = dict(zip(names, ages))


class Foo():
    def __init__(self, a, b):
        self.a = a
        self.b = b


foo = Foo(1, 2)
print(foo.__dict__)

Functions

I have never considered Python to be heavily influenced by functional languages, no matter what people say or think. I was much more familiar with imperative languages such as C and Algol 68 and although I had made functions first-class objects, I didn’t view Python as a functional programming language. — Guido van Rossum, Python BDFL

def factorial(n):
    return 1 if n < 2 else n * factorial(n-1)


factorial = lambda n: 1 if n < 2 else n * factorial(n-1)

# high-order function

>>> fruits = ['strawberry', 'fig', 'apple', 'cherry', 'raspberry', 'banana']
>>> sorted(fruits, key=len)
['fig', 'apple', 'cherry', 'banana', 'raspberry', 'strawberry']

# From positional to keyword-only parameters

def tag(name, *content, cls=None, **attrs):
    if cls is not None:
        attrs['class'] = cls

    if attrs:
        attrs_str = ''.join(f' {attr}="{value}"'
                            for attr, value
                            in sorted(attrs.items()))
    else:
        attrs_str = ''

    if content:
        return '\n'.join(f'<{name}{attrs_str}>{c}</{name}>'
                         for c in content)
    else:
        return f'<{name}{attrs_str} />'

'''
>>> tag('br')
'<br />'
>>> print(tag('p', 'hello', 'world'))
<p>hello</p>
<p>world</p>
>>> tag('p', 'hello', id=33)
'<p id="33">hello</p>'
>>> print(tag('p', 'hello', 'world', cls='sidebar'))
<p class="sidebar">hello</p>
<p class="sidebar">world</p>
>>> tag(content='testing', name="img")
'<img content="testing" />'
>>> my_tag = {'name': 'img', 'title': 'Sunset Boulevard',
...           'src': 'sunset.jpg', 'cls': 'framed'}
>>>
>>> tag(**my_tag)
'<img class="framed" src="sunset.jpg" title="Sunset Boulevard" />'
'''

# Function annotations

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

'''
>>> sum(1, 2)
3
>>> sum(1, 2.0)
3.0
>>> sum.__annotations__
{'a': <class 'int'>, 'b': <class 'int'>, 'return': <class 'int'>}
'''

# Decorators

def memoize(f):
    memo = {}
    def helper(x):
        if x not in memo:
            memo[x] = f(x)
        return memo[x]
    return helper


@memoize
def fib(n):
    if n == 0:
        return 0
    elif n == 1:
        return 1
    else:
        return fib(n - 1) + fib(n - 2)

http://www.pythontutor.com/visualize.html#mode=edit

Class

class LineItem:
    def __init__(self, product, quantity, price):
        self.product = product
        self.quantity = quantity
        self.price = price

    def total(self):
        return self.price * self.quantity

# Inheritance

class AnswerDict(dict):
    def __getitem__(self, key):
        return 42

>>> ad = AnswerDict(a='foo')
>>> ad['a']
42

# Protocols and duck typing

# The sequence protocol in Python entails just the __len__ and __getitem__ methods. Any class that implements those methods with the standard signature and semantics can be used anywhere a sequence is expected.

class Vector:
    def __init__(self, components):
        self.components = list(components)

    def __len__(self):
        return len(self.components)

    def __getitem__(self, index):
        return self.components[index]
        
v = Vector(range(10))
print(v[0])
print(len(v))

for i in v:
    print(i)

# Operator overloading

import sys

class ostream:
    def __init__(self, file):
        self.file = file

    def __lshift__(self, obj):
        self.file.write(str(obj));
        return self

cout = ostream(sys.stdout)
cerr = ostream(sys.stderr)
nl = '\n'

x = 'hello'
y = 'world'
cout << x << " " << y << nl

Generator

>>> (i for i in range(10))
<generator object <genexpr> at 0x10d856468>
>>> [i for i in range(10)]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]


def tokenize(text):
    for w in text.split():
        yield w

'''
>>> words = tokenize('this is a sample sentence')
>>> words
<generator object tokenize at 0x10d84c360>
>>> next(words)
'this'
>>> next(words)
'is'
>>> for w in words:
...     print(w)
...
a
sample
sentence
'''

Context Manager

Similar with try-with-resources statement in Java.

with open('file.txt') as f:
    for line in f:
        print(line)


# create our own contextmanager

from contextlib import contextmanager

@contextmanager
def myopen(file_path):
    f = open(file_path)
    try:
        yield f
    finally:
        f.close()


with myopen('file.txt') as f:
    for line in f:
        print(line)

Performance

# profile

$ python -m cProfile ostream.py
hello world
         15 function calls in 0.000 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        4    0.000    0.000    0.000    0.000 ostream.py:10(__lshift__)
        1    0.000    0.000    0.000    0.000 ostream.py:4(<module>)
        1    0.000    0.000    0.000    0.000 ostream.py:6(ostream)
        2    0.000    0.000    0.000    0.000 ostream.py:7(__init__)
        1    0.000    0.000    0.000    0.000 {built-in method builtins.__build_class__}
        1    0.000    0.000    0.000    0.000 {built-in method builtins.exec}
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
        4    0.000    0.000    0.000    0.000 {method 'write' of '_io.TextIOWrapper' objects}

# timeit
        
def div1(a, b):
    try:
        return a / b
    except ZeroDivisionError:
        pass


def div2(a, b):
    if b != 0:
        return a / b

'''
>>> timeit.timeit('div1(2, 3)', globals=globals())
0.21480855904519558
>>> timeit.timeit('div2(2, 3)', globals=globals())
0.24475659406743944

# dis

>>> dis.dis(div1)
  2           0 SETUP_EXCEPT             8 (to 10)
  3           2 LOAD_FAST                0 (a)
              4 LOAD_FAST                1 (b)
              6 BINARY_TRUE_DIVIDE
              8 RETURN_VALUE
  4     >>   10 DUP_TOP
             12 LOAD_GLOBAL              0 (ZeroDivisionError)
             14 COMPARE_OP              10 (exception match)
             16 POP_JUMP_IF_FALSE       28
             18 POP_TOP
             20 POP_TOP
             22 POP_TOP
  5          24 POP_EXCEPT
             26 JUMP_FORWARD             2 (to 30)
        >>   28 END_FINALLY
        >>   30 LOAD_CONST               0 (None)
             32 RETURN_VALUE
>>> dis.dis(div2)
  2           0 LOAD_FAST                1 (b)
              2 LOAD_CONST               1 (0)
              4 COMPARE_OP               3 (!=)
              6 POP_JUMP_IF_FALSE       16
  3           8 LOAD_FAST                0 (a)
             10 LOAD_FAST                1 (b)
             12 BINARY_TRUE_DIVIDE
             14 RETURN_VALUE
        >>   16 LOAD_CONST               0 (None)
             18 RETURN_VALUE
'''

What's not covered

  • Coroutines
  • Concurrency with futures
  • Concurrency with asyncio
  • Metaprogramming
  • Cython: C-Extensions for Python

Further reading

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment