In this document, we are going to be talking about some Python idioms.
- high level design principles
- design patterns
- idioms
- code style
I am not an expert.
>>> 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!
https://www.python.org/dev/peps/pep-0008/
- Data structures
- Functions
- Classes
- Control flow
- Performance
- Elio
- ABC
- RegEx
False values:
False, None, 0, '', [], {}, set()
42
10000000000000000000
12.34
1 + 2j
# 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}'
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))
- 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
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__)
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 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
>>> (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
'''
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)
# 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
'''
- Coroutines
- Concurrency with futures
- Concurrency with asyncio
- Metaprogramming
- Cython: C-Extensions for Python
- PEP 8: https://www.python.org/dev/peps/pep-0008/
- PEP 3099 -- Things that will Not Change in Python 3000: https://www.python.org/dev/peps/pep-3099/
- Transforming Code Into Beautiful, Idiomatic Python: https://www.youtube.com/watch?v=anrOzOapJ2E
- Fluent Python: Clear, Concise, and Effective Programming
- http://www.pythontutor.com/visualize.html#mode=edit