Mastering Python Second Edition Release Code

This commit is contained in:
Rick van Hattem 2022-05-05 18:25:55 +02:00
commit 3223a43fe3
454 changed files with 20230 additions and 0 deletions

82
.gitignore vendored Normal file
View File

@ -0,0 +1,82 @@
# Pytest files and test output files
.xprocess/
*.txt
/*.log
/*.json
*.lprof
*.profile
*.valgrind
*.callgrind
*.pickle
*.sqlite
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
.mypy_cache/
.pytest_cache/
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
.hypothesis/
# Translations
*.mo
*.pot
# Django stuff:
*.log
# Sphinx documentation
_build/
# PyBuilder
target/
#Ipython Notebook
.ipynb_checkpoints
.vimrc
.python-version
*.cover
# Ignore Dask files
dask-worker-space

33
.travis.yml Normal file
View File

@ -0,0 +1,33 @@
# Config file for automatic testing at travis-ci.org
sudo: false
language: python
env:
global:
- PIP_WHEEL_DIR=$HOME/.wheels
- PIP_FIND_LINKS=file://$PIP_WHEEL_DIR
matrix:
include:
- python: '3.8'
env: TOXENV=py38
cache:
directories:
- $HOME/.wheels
# command to install dependencies, e.g. pip install -r requirements.txt
install:
- mkdir -p $PIP_WHEEL_DIR
- pip wheel -r requirements.txt
- pip install tox
script:
- tox
notifications:
email:
on_success: never
on_failure: change

View File

@ -0,0 +1,5 @@
Chapter 1, Getting started
##############################################################################
| One Environment per Project introduces virtual Python environments using
| virtualenv or venv to isolate the packages in your Python projects.

View File

@ -0,0 +1,53 @@
[[package]]
name = "progressbar2"
version = "3.55.0"
description = "A Python Progressbar library to provide visual (yet text based) progress to long running operations."
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
python-utils = ">=2.3.0"
six = "*"
[package.extras]
docs = ["sphinx (>=1.7.4)"]
tests = ["flake8 (>=3.7.7)", "pytest (>=4.6.9)", "pytest-cov (>=2.6.1)", "freezegun (>=0.3.11)", "sphinx (>=1.8.5)"]
[[package]]
name = "python-utils"
version = "2.5.6"
description = "Python Utils is a module with some convenient utilities not included with the standard Python install"
category = "main"
optional = false
python-versions = "*"
[package.dependencies]
six = "*"
[[package]]
name = "six"
version = "1.16.0"
description = "Python 2 and 3 compatibility utilities"
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
[metadata]
lock-version = "1.1"
python-versions = "^3.10"
content-hash = "5da1cef67396e7888aff720cf71ea44c6291a7f61df8cec6ae2fd7a0d7d23075"
[metadata.files]
progressbar2 = [
{file = "progressbar2-3.55.0-py2.py3-none-any.whl", hash = "sha256:e98fee031da31ab9138fd8dd838ac80eafba82764eb75a43d25e3ca622f47d14"},
{file = "progressbar2-3.55.0.tar.gz", hash = "sha256:86835d1f1a9317ab41aeb1da5e4184975e2306586839d66daf63067c102f8f04"},
]
python-utils = [
{file = "python-utils-2.5.6.tar.gz", hash = "sha256:352d5b1febeebf9b3cdb9f3c87a3b26ef22d3c9e274a8ec1e7048ecd2fac4349"},
{file = "python_utils-2.5.6-py2.py3-none-any.whl", hash = "sha256:18fbc1a1df9a9061e3059a48ebe5c8a66b654d688b0e3ecca8b339a7f168f208"},
]
six = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
]

View File

@ -0,0 +1,15 @@
[tool.poetry]
name = "t_00_poetry"
version = "0.1.0"
description = ""
authors = ["Rick van Hattem <Wolph@wol.ph>"]
[tool.poetry.dependencies]
python = "^3.10"
progressbar2 = "^3.1.0"
[tool.poetry.dev-dependencies]
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"

View File

View File

@ -0,0 +1,3 @@
Chapter 2, Interactive Python Shells
##############################################################################

View File

@ -0,0 +1,2 @@
>>> 'Hello world!'
'Hello world!'

View File

@ -0,0 +1,27 @@
# Adding default imports
>>> from pprint import pprint as pp
>>> from pprint import pformat as pf
>>> pp(dict(spam=0xA, eggs=0xB))
{'eggs': 11, 'spam': 10}
>>> pf(dict(spam=0xA, eggs=0xB))
"{'eggs': 11, 'spam': 10}"
# Modifying prompt
>>> if True:
... print('Hello!')
Hello!
>>> import sys
>>> sys.ps1 = '> '
>>> sys.ps2 = '. '
# With modified prompt
> if True:
. print('Hello!')
Hello!

View File

@ -0,0 +1,76 @@
import __main__
import re
import atexit
import readline
import rlcompleter
class Completer(rlcompleter.Completer):
ITEM_RE = re.compile(r'(?P<expression>.+?)\[(?P<key>[^\[]*)')
def complete(self, text, state):
# Init namespace. From `rlcompleter.Completer.complete`
if self.use_main_ns:
self.namespace = __main__.__dict__
# If we find a [, try and return the keys
if '[' in text:
# At state 0 we need to prefetch the matches, after
# that we use the cached results
if state == 0:
self.matches = list(self.item_matches(text))
# Try and return the match if it exists
try:
return self.matches[state]
except IndexError:
pass
else:
# Fallback to the normal completion
return super().complete(text, state)
def item_matches(self, text):
# Look for the pattern expression[key
match = self.ITEM_RE.match(text)
if match:
search_key = match.group('key').lstrip()
expression = match.group('expression')
# Strip quotes from the key
if search_key and search_key[0] in {"'", '"'}:
search_key = search_key.strip(search_key[0])
# Fetch the object from the namespace
object_ = eval(expression, self.namespace)
# Duck typing, check if we have a `keys()` attribute
if hasattr(object_, 'keys'):
# Fetch the keys by executing the `keys()` method
# Can you guess where the bug is?
keys = object_.keys()
for i, key in enumerate(keys):
# Limit to 25 items for safety, could be
# infinite
if i >= 25:
break
# Only return matching results
if key.startswith(search_key):
yield f'{expression}[{key!r}]'
# By default readline doesn't call the autocompleter for [ because
# it's considered a delimiter. With a little bit of work we can
# fix this however :)
delims = readline.get_completer_delims()
# Remove [, ' and " from the delimiters
delims = delims.replace('[', '').replace('"', '').replace("'", '')
# Set the delimiters
readline.set_completer_delims(delims)
# Create and set the completer
completer = Completer()
readline.set_completer(completer.complete)
# Add a cleanup call on Python exit
atexit.register(lambda: readline.set_completer(None))

View File

@ -0,0 +1,15 @@
>>> sandwich = dict(spam=2, eggs=1, sausage=1)
>>> sandwich.<TAB> # doctest: +SKIP
sandwich.clear( sandwich.fromkeys( sandwich.items( sandwich.pop(
sandwich.setdefault( sandwich.values( sandwich.copy( sandwich.get(
sandwich.keys( sandwich.popitem( sandwich.update(
>>> sandwich[ # doctest: +SKIP
>>> sandwich = dict(spam=2, eggs=1, sausage=1)
>>> sandwich['<TAB> # doctest: +SKIP
sandwich['eggs'] sandwich['sausage'] sandwich['spam']
>>> import T_02_modifying_autocompletion
>>> autocompletion = T_02_modifying_autocompletion

View File

@ -0,0 +1,25 @@
Note: missing_ok was added in Python 3.8
>>> import pathlib
>>> pathlib.Path('bpython.txt').unlink(missing_ok=True)
>>> with open('bpython.txt', 'a') as fh:
... fh.write('x')
...
1
>>> with open('bpython.txt') as fh:
... print(fh.read())
...
x
>>> sandwich = dict(spam=2, eggs=1, sausage=1)
>>> with open('bpython.txt', 'a') as fh:
... fh.write('x')
...
1
>>> with open('bpython.txt') as fh:
... print(fh.read())
...
xx
>>>

View File

View File

@ -0,0 +1,4 @@
with open('reload.txt', 'a+') as fh:
fh.write('x')
fh.seek(0)
print(fh.read())

View File

@ -0,0 +1,7 @@
import sys
import pathlib
# Little hack to add the current directory to sys.path so we can
# find the imports
path = pathlib.Path(__file__).parent
sys.path.append(str(path.resolve()))

View File

@ -0,0 +1 @@
xxxxx

View File

@ -0,0 +1,3 @@
# coding: utf-8
sandwich = dict(spam=2, eggs=1, sausage=1)

View File

@ -0,0 +1,4 @@
Chapter 2, Pythonic Syntax
##############################################################################
| Common Pitfalls and Style Guide explains what Pythonic code is and how to write code that is Pythonic and adheres to the Python philosophy.

View File

@ -0,0 +1,78 @@
# Simple formatting
>>> name = 'Rick'
>>> 'Hi %s' % name
'Hi Rick'
>>> 'Hi {}'.format(name)
'Hi Rick'
>>> value = 1 / 3
>>> '%.2f' % value
'0.33'
>>> '{:.2f}'.format(value)
'0.33'
>>> name = 'Rick'
>>> value = 1 / 3
>>> 'Hi {0}, value: {1:.3f}. Bye {0}'.format(name, value)
'Hi Rick, value: 0.333. Bye Rick'
# Named variables
>>> name = 'Rick'
>>> 'Hi %(name)s' % dict(name=name)
'Hi Rick'
>>> 'Hi {name}'.format(name=name)
'Hi Rick'
>>> f'Hi {name}'
'Hi Rick'
>>> 'Hi {name}'.format(**globals())
'Hi Rick'
# Arbitrary expressions
## Accessing dict items
>>> username = 'wolph'
>>> a = 123
>>> b = 456
>>> some_dict = dict(a=a, b=b)
>>> f'''a: {some_dict['a']}'''
'a: 123'
>>> f'''sum: {some_dict['a'] + some_dict['b']}'''
'sum: 579'
## Python expressions, specifically an inline if statement
>>> f'if statement: {a if a > b else b}'
'if statement: 456'
## Function calls
>>> f'min: {min(a, b)}'
'min: 123'
>>> f'Hi {username}. And in uppercase: {username.upper()}'
'Hi wolph. And in uppercase: WOLPH'
## Loops
>>> f'Squares: {[x ** 2 for x in range(5)]}'
'Squares: [0, 1, 4, 9, 16]'

View File

@ -0,0 +1,22 @@
>>> import this
The Zen of Python, by Tim Peters
<BLANKLINE>
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!

View File

@ -0,0 +1,14 @@
>>> filter_modulo = lambda i, m: (i[j] for j in \
... range(len(i)) if i[j] % m)
>>> list(filter_modulo(range(10), 2))
[1, 3, 5, 7, 9]
>>> def filter_modulo(items, modulo):
... for item in items:
... if item % modulo:
... yield item
...
>>> list(filter_modulo(range(10), 2))
[1, 3, 5, 7, 9]

View File

@ -0,0 +1,46 @@
>>> from os import *
>>> from asyncio import *
>>> assert wait
>>> from os import path
>>> from asyncio import wait
>>> assert wait
>>> import os
>>> import asyncio
>>> assert asyncio.wait
>>> assert os.path
>>> import concurrent.futures
>>> assert concurrent.futures.wait
>>> def spam(eggs, *args, **kwargs):
... for arg in args:
... eggs += arg
... for extra_egg in kwargs.get('extra_eggs', []):
... eggs += extra_egg
... return eggs
>>> spam(1, 2, 3, extra_eggs=[4, 5])
15
>>> def sum_ints(*args):
... total = 0
... for arg in args:
... total += arg
... return total
>>> sum_ints(1, 2, 3, 4, 5)
15

View File

@ -0,0 +1,47 @@
>>> import math
>>> import itertools
>>> def primes_complicated():
... sieved = dict()
... i = 2
...
... while True:
... if i not in sieved:
... yield i
... sieved[i * i] = [i]
... else:
... for j in sieved[i]:
... sieved.setdefault(i + j, []).append(j)
... del sieved[i]
...
... i += 1
>>> list(itertools.islice(primes_complicated(), 10))
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
>>> def primes_complex():
... numbers = itertools.count(2)
... while True:
... yield (prime := next(numbers))
... numbers = filter(prime.__rmod__, numbers)
>>> list(itertools.islice(primes_complex(), 10))
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
>>> def is_prime(number):
... if number == 0 or number == 1:
... return False
... for modulo in range(2, number):
... if not number % modulo:
... return False
... else:
... return True
>>> def primes_simple():
... for i in itertools.count():
... if is_prime(i):
... yield i
>>> list(itertools.islice(primes_simple(), 10))
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]

View File

@ -0,0 +1,27 @@
>>> def between_and_modulo(value, a, b, modulo):
... if value >= a:
... if value <= b:
... if value % modulo:
... return True
... return False
>>> for i in range(10):
... if between_and_modulo(i, 2, 9, 2):
... print(i, end=' ')
3 5 7 9
>>> def between_and_modulo(value, a, b, modulo):
... if value < a:
... return False
... elif value > b:
... return False
... elif not value % modulo:
... return False
... else:
... return True
>>> for i in range(10):
... if between_and_modulo(i, 2, 9, 2):
... print(i, end=' ')
3 5 7 9

View File

@ -0,0 +1,11 @@
>>> f=lambda x:0**x or x*f(x-1)
>>> f(40)
815915283247897734345611269596115894272000000000
>>> def factorial(x):
... if 0 ** x:
... return 1
... else:
... return x * factorial(x - 1)
>>> factorial(40)
815915283247897734345611269596115894272000000000

View File

@ -0,0 +1,32 @@
>>> from functools import reduce
>>> fib=lambda n:n if n<2 else fib(n-1)+fib(n-2)
>>> fib(10)
55
>>> fib=lambda n:reduce(lambda x,y:(x[0]+x[1],x[0]),[(1,1)]*(n-1))[0]
>>> fib(10)
55
>>> def fib(n):
... if n < 2:
... return n
... else:
... return fib(n - 1) + fib(n - 2)
>>> fib(10)
55
>>> def fib(n):
... a = 0
... b = 1
... for _ in range(n):
... a, b = b, a + b
...
... return a
>>> fib(10)
55

View File

@ -0,0 +1,18 @@
>>> from concurrent.futures import ProcessPoolExecutor, \
... CancelledError, TimeoutError
>>> from concurrent.futures import (
... ProcessPoolExecutor, CancelledError, TimeoutError)
>>> from concurrent import futures
>>> from concurrent.futures.process import (
... ProcessPoolExecutor
... )
>>> from concurrent.futures import (
... ProcessPoolExecutor,
... CancelledError,
... TimeoutError,
... )

View File

@ -0,0 +1,51 @@
>>> some_user_input = '123abc'
>>> try:
... value = int(some_user_input)
... except:
... pass
>>> some_user_input = '123abc'
>>> try:
... value = int(some_user_input)
... except ValueError:
... pass
>>> import logging
>>> some_user_input = '123abc'
>>> try:
... value = int(some_user_input)
... except Exception as exception:
... logging.exception('Uncaught: {exception!r}')
>>> some_user_input_a = '123'
>>> some_user_input_b = 'abc'
>>> try:
... value = int(some_user_input_a)
... value += int(some_user_input_b)
... except:
... value = 0
>>> try:
... 1 / 0 # Raises ZeroDivisionError
... except ZeroDivisionError:
... print('Got zero division error')
... except Exception as exception:
... print(f'Got unexpected exception: {exception}')
... except BaseException as exception:
... # Base exceptions are a special case for keyboard
... # interrupts and a few other exceptions that are not
... # technically errors.
... print(f'Got base exception: {exception}')
... else:
... print('No exceptions happened, we can continue')
... finally:
... # Useful cleanup functions such as closing a file
... print('This code is _always_ executed')
Got zero division error
This code is _always_ executed

View File

@ -0,0 +1,7 @@
>>> fh_a = open('spam', 'w', -1, None, None, '\n')
>>> fh_b = open(file='spam', mode='w', buffering=-1, newline='\n')
>>> filename = 'spam'
>>> mode = 'w'
>>> buffers = -1
>>> fh_b = open(filename, mode, buffers, newline='\n')

View File

@ -0,0 +1,3 @@
>>> import warnings
>>> warnings.warn('Something deprecated', DeprecationWarning)

View File

@ -0,0 +1,9 @@
>>> from json import loads
>>> loads('{}')
{}
>>> import json
>>> json.loads('{}')
{}

View File

@ -0,0 +1,28 @@
>>> timestamp = 12345
>>> filename = f'{timestamp}.csv'
>>> import datetime
>>> timestamp = 12345
>>> if isinstance(timestamp, datetime.datetime):
... filename = f'{timestamp}.csv'
... else:
... raise TypeError(f'{timestamp} is not a valid datetime')
Traceback (most recent call last):
...
TypeError: 12345 is not a valid datetime
>>> import datetime
>>> timestamp = datetime.date(2000, 10, 5)
>>> filename = f'{timestamp}.csv'
>>> print(f'Filename from date: {filename}')
Filename from date: 2000-10-05.csv
>>> timestamp = '2000-10-05'
>>> filename = f'{timestamp}.csv'
>>> print(f'Filename from str: {filename}')
Filename from str: 2000-10-05.csv

View File

@ -0,0 +1,58 @@
>>> a = 1
>>> a == True
True
>>> a is True
False
>>> b = 0
>>> b == False
True
>>> b is False
False
>>> def some_unsafe_function(arg=None):
... if not arg:
... arg = 123
...
... return arg
>>> some_unsafe_function(0)
123
>>> some_unsafe_function(None)
123
>>> def some_safe_function(arg=None):
... if arg is None:
... arg = 123
...
... return arg
>>> some_safe_function(0)
0
>>> some_safe_function(None)
123
>>> a = 200 + 56
>>> b = 256
>>> c = 200 + 57
>>> d = 257
>>> a == b
True
>>> a is b
True
>>> c == d
True
>>> c is d
False
>>> spam = list(range(1000000))
>>> eggs = list(range(1000000))
>>> spam == eggs
True
>>> spam is eggs
False

View File

@ -0,0 +1,21 @@
>>> my_range = range(5)
>>> i = 0
>>> while i < len(my_range):
... item = my_range[i]
... print(i, item, end=', ')
... i += 1
0 0, 1 1, 2 2, 3 3, 4 4,
>>> my_range = range(5)
>>> for item in my_range:
... print(item, end=', ')
0, 1, 2, 3, 4,
>>> for i, item in enumerate(my_range):
... print(i, item, end=', ')
0 0, 1 1, 2 2, 3 3, 4 4,
>>> my_range = range(5)
>>> [(i, item) for i, item in enumerate(my_range)]
[(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)]

View File

@ -0,0 +1,18 @@
def noop():
pass
def yield_cube_points(matrix):
for x in matrix:
for y in x:
for z in y:
yield (x, y, z)
def print_cube(matrix):
for x in matrix:
for y in x:
for z in y:
print(z, end='')
print()
print()

View File

@ -0,0 +1,2 @@
some_number: int
some_number = 'test'

View File

@ -0,0 +1,2 @@
def spam(a,b,c):print(a,b+c)
def eggs():pass

View File

@ -0,0 +1,182 @@
>>> some_variable = 123
>>> match some_variable:
... case 1:
... print('Got 1')
... case 2:
... print('Got 2')
... case _:
... print('Got something else')
Got something else
>>> if some_variable == 1:
... print('Got 1')
... elif some_variable == 1:
... print('Got 2')
... else:
... print('Got something else')
Got something else
##################################################################
>>> some_variable = 123
>>> match some_variable:
... case 1:
... print('Got 1')
... case other:
... print('Got something else:', other)
Got something else: 123
##################################################################
>>> class Direction:
... LEFT = -1
... RIGHT = 1
>>> some_variable = Direction.LEFT
>>> match some_variable:
... case Direction.LEFT:
... print('Going left')
... case Direction.RIGHT:
... print('Going right')
Going left
##################################################################
>>> class Direction:
... LEFT = -1
... UP = 0
... RIGHT = 1
... DOWN = 2
>>> some_variable = Direction.LEFT
>>> match some_variable:
... case Direction.LEFT | Direction.RIGHT:
... print('Going horizontal')
... case Direction.UP | Direction.DOWN:
... print('Going vertical')
Going horizontal
##################################################################
>>> values = -1, 0, 1
>>> for value in values:
... print('matching', value, end=': ')
... match value:
... case negative if negative < 0:
... print(f'{negative} is smaller than 0')
... case positive if positive > 0:
... print(f'{positive} is greater than 0')
... case _:
... print('no match')
matching -1: -1 is smaller than 0
matching 0: no match
matching 1: 1 is greater than 0
##################################################################
>>> values = (0, 1), (0, 2), (1, 2)
>>> for value in values:
... print('matching', value, end=': ')
... match value:
... case 0, 1:
... print('exactly matched 0, 1')
... case 0, y:
... print(f'matched 0, y with y: {y}')
... case x, y:
... print(f'matched x, y with x, y: {x}, {y}')
matching (0, 1): exactly matched 0, 1
matching (0, 2): matched 0, y with y: 2
matching (1, 2): matched x, y with x, y: 1, 2
##################################################################
>>> def get_uri(*args):
... # Set defaults so we only have to store changed variables
... protocol, port, paths = 'https', 443, ()
... match args:
... case (hostname,):
... pass
... case (hostname, port):
... pass
... case (hostname, port, protocol, *paths):
... pass
... case _:
... raise RuntimeError(f'Invalid arguments {args}')
...
... path = '/'.join(paths)
... return f'{protocol}://{hostname}:{port}/{path}'
>>> get_uri('localhost')
'https://localhost:443/'
>>> get_uri('localhost', 12345)
'https://localhost:12345/'
>>> get_uri('localhost', 80, 'http')
'http://localhost:80/'
>>> get_uri('localhost', 80, 'http', 'some', 'paths')
'http://localhost:80/some/paths'
##################################################################
>>> values = (0, 1), (0, 2), (1, 2)
>>> for value in values:
... print('matching', value, end=': ')
... match value:
... case 0 as x, (1 | 2) as y:
... print(f'matched x, y with x, y: {x}, {y}')
... case _:
... print('no match')
matching (0, 1): matched x, y with x, y: 0, 1
matching (0, 2): matched x, y with x, y: 0, 2
matching (1, 2): no match
##################################################################
>>> values = dict(a=0, b=0), dict(a=0, b=1), dict(a=1, b=1)
>>> for value in values:
... print('matching', value, end=': ')
... match value:
... case {'a': 0}:
... print('matched a=0:', value)
... case {'a': 0, 'b': 0}:
... print('matched a=0, b=0:', value)
... case _:
... print('no match')
matching {'a': 0, 'b': 0}: matched a=0: {'a': 0, 'b': 0}
matching {'a': 0, 'b': 1}: matched a=0: {'a': 0, 'b': 1}
matching {'a': 1, 'b': 1}: no match
##################################################################
>>> class Person:
... def __init__(self, name):
... self.name = name
>>> values = Person('Rick'), Person('Guido')
>>> for value in values:
... match value:
... case Person(name='Rick'):
... print('I found Rick')
... case Person(occupation='Programmer'):
... print('I found a programmer')
... case Person() as person:
... print('I found a person:', person.name)
I found Rick
I found a person: Guido
##################################################################
>>> class Person:
... def __init__(self, name):
... self.name = name
>>> value = Person(123)
>>> match value:
... case Person(name=str() as name):
... print('Found person with str name:', name)
... case Person(name=int() as name):
... print('Found person with int name:', name)
Found person with int name: 123

View File

@ -0,0 +1,18 @@
>>> g = 1
>>> def print_global():
... print(f'Value: {g}')
>>> print_global()
Value: 1
>>> g = 1
>>> def print_global():
... g += 1
... print(f'Value: {g}')
>>> print_global()
Traceback (most recent call last):
...
UnboundLocalError: local variable 'g' referenced before assignment

View File

@ -0,0 +1,37 @@
>>> import copy
>>> x = [[1], [2, 3]]
>>> y = x.copy()
>>> z = copy.deepcopy(x)
>>> x.append('a')
>>> x[0].append(x)
>>> x
[[1, [...]], [2, 3], 'a']
>>> y
[[1, [...]], [2, 3]]
>>> z
[[1], [2, 3]]
>>> def append(list_=[], value='value'):
... list_.append(value)
... return list_
>>> append(value='a')
['a']
>>> append(value='b')
['a', 'b']
>>> def append(list_=None, value='value'):
... if list_ is None:
... list_ = []
... list_.append(value)
... return list_
>>> append(value='a')
['a']
>>> append(value='b')
['b']

View File

@ -0,0 +1,47 @@
>>> class SomeClass:
... class_list = []
...
... def __init__(self):
... self.instance_list = []
>>> SomeClass.class_list.append('from class')
>>> instance = SomeClass()
>>> instance.class_list.append('from instance')
>>> instance.instance_list.append('from instance')
>>> SomeClass.class_list
['from class', 'from instance']
>>> SomeClass.instance_list
Traceback (most recent call last):
...
AttributeError: ... 'SomeClass' has no attribute 'instance_list'
>>> instance.class_list
['from class', 'from instance']
>>> instance.instance_list
['from instance']
>>> class Parent:
... pass
>>> class Child(Parent):
... pass
>>> Parent.parent_property = 'parent'
>>> Child.parent_property
'parent'
>>> Child.parent_property = 'child'
>>> Parent.parent_property
'parent'
>>> Child.parent_property
'child'
>>> Child.child_property = 'child'
>>> Parent.child_property
Traceback (most recent call last):
...
AttributeError: ... 'Parent' has no attribute 'child_property'

View File

@ -0,0 +1,32 @@
import builtins
import inspect
import pprint
import re
def pp(*args, **kwargs):
'''PrettyPrint function that prints the variable name when
available and pprints the data
>>> x = 10
>>> pp(x)
# x: 10
'''
# Fetch the current frame from the stack
frame = inspect.currentframe().f_back
# Prepare the frame info
frame_info = inspect.getframeinfo(frame)
# Walk through the lines of the function
for line in frame_info[3]:
# Search for the pp() function call with a fancy regexp
m = re.search(r'\bpp\s*\(\s*([^)]*)\s*\)', line)
if m:
print('# %s:' % m.group(1), end=' ')
break
pprint.pprint(*args, **kwargs)
builtins.pf = pprint.pformat
builtins.pp = pp

View File

@ -0,0 +1,14 @@
>>> list = list((1, 2, 3))
>>> list
[1, 2, 3]
>>> list((4, 5, 6))
Traceback (most recent call last):
...
TypeError: 'list' object is not callable
>>> import = 'Some import'
Traceback (most recent call last):
...
SyntaxError: invalid syntax

View File

@ -0,0 +1,35 @@
>>> dict_ = dict(a=123)
>>> set_ = set((456,))
>>> for key in dict_:
... del dict_[key]
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
RuntimeError: dictionary changed size during iteration
>>> for item in set_:
... set_.remove(item)
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
RuntimeError: Set changed size during iteration
>>> list_ = list(range(10))
>>> list_
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> for item in list_:
... print(list_.pop(0), end=', ')
0, 1, 2, 3, 4,
>>> list_
[5, 6, 7, 8, 9]
>>> list_ = list(range(10))
>>> for item in list(list_):
... print(list_.pop(0), end=', ')
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,

View File

@ -0,0 +1,20 @@
>>> exception = None
>>> try:
... 1 / 0
... except ZeroDivisionError as exception:
... pass
>>> exception
Traceback (most recent call last):
...
NameError: name 'exception' is not defined
>>> try:
... 1 / 0
... except ZeroDivisionError as exception:
... new_exception = exception
>>> new_exception
ZeroDivisionError('division by zero')

View File

@ -0,0 +1,14 @@
>>> functions = [lambda: i for i in range(3)]
>>> for function in functions:
... print(function(), end=', ')
2, 2, 2,
>>> from functools import partial
>>> functions = [partial(lambda x: x, i) for i in range(3)]
>>> for function in functions:
... print(function(), end=', ')
0, 1, 2,

View File

@ -0,0 +1,9 @@
import T_28_circular_imports_b
class FileA:
pass
class FileC(T_28_circular_imports_b.FileB):
pass

View File

@ -0,0 +1,5 @@
import T_28_circular_imports_a
class FileB(T_28_circular_imports_a.FileA):
pass

View File

@ -0,0 +1,2 @@
class FileA:
pass

View File

@ -0,0 +1,5 @@
import T_29_circular_imports_a
class FileB(T_29_circular_imports_a.FileA):
pass

View File

@ -0,0 +1,5 @@
import T_29_circular_imports_b
class FileC(T_29_circular_imports_b.FileB):
pass

View File

@ -0,0 +1,10 @@
>>> import importlib
>>> module_name = 'sys'
>>> attribute = 'version_info'
>>> module = importlib.import_module(module_name)
>>> module
<module 'sys' (built-in)>
>>> getattr(module, attribute).major
3

View File

View File

@ -0,0 +1,7 @@
import sys
import pathlib
# Little hack to add the current directory to sys.path so we can
# find the imports
path = pathlib.Path(__file__).parent
sys.path.append(str(path.resolve()))

View File

@ -0,0 +1,4 @@
Chapter 3, Containers and Collections
------------------------------------------------------------------------------
| Storing Data the Right Way using the many containers and collections bundled with Python to create code that is fast and readable.

View File

@ -0,0 +1,34 @@
>>> n = 1000
>>> a = list(range(n))
>>> b = dict.fromkeys(range(n))
>>> for i in range(100):
... assert i in a # takes n=1000 steps
... assert i in b # takes 1 step
>>> def o_one(items):
... return 1 # 1 operation so O(1)
>>> def o_n(items):
... total = 0
... # Walks through all items once so O(n)
... for item in items:
... total += item
... return total
>>> def o_n_squared(items):
... total = 0
... # Walks through all items n*n times so O(n**2)
... for a in items:
... for b in items:
... total += a * b
... return total
>>> n = 10
>>> items = range(n)
>>> o_one(items) # 1 operation
1
>>> o_n(items) # n = 10 operations
45
>>> o_n_squared(items) # n*n = 10*10 = 100 operations
2025

View File

@ -0,0 +1,89 @@
>>> def remove(items, value):
... new_items = []
... found = False
... for item in items:
... # Skip the first item which is equal to value
... if not found and item == value:
... found = True
... continue
... new_items.append(item)
...
... if not found:
... raise ValueError('list.remove(x): x not in list')
...
... return new_items
>>> def insert(items, index, value):
... new_items = []
... for i, item in enumerate(items):
... if i == index:
... new_items.append(value)
... new_items.append(item)
... return new_items
>>> items = list(range(10))
>>> items
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> items = remove(items, 5)
>>> items
[0, 1, 2, 3, 4, 6, 7, 8, 9]
>>> items = insert(items, 2, 5)
>>> items
[0, 1, 5, 2, 3, 4, 6, 7, 8, 9]
------------------------------------------------------------------------------
>>> primes = set((1, 2, 3, 5, 7))
# Classic solution
>>> items = list(range(10))
>>> for prime in primes:
... items.remove(prime)
>>> items
[0, 4, 6, 8, 9]
# List comprehension
>>> items = list(range(10))
>>> [item for item in items if item not in primes]
[0, 4, 6, 8, 9]
# Filter
>>> items = list(range(10))
>>> list(filter(lambda item: item not in primes, items))
[0, 4, 6, 8, 9]
------------------------------------------------------------------------------
>>> def in_(items, value):
... for item in items:
... if item == value:
... return True
... return False
>>> def min_(items):
... current_min = items[0]
... for item in items[1:]:
... if current_min > item:
... current_min = item
... return current_min
>>> def max_(items):
... current_max = items[0]
... for item in items[1:]:
... if current_max < item:
... current_max = item
... return current_max
>>> items = range(5)
>>> in_(items, 3)
True
>>> min_(items)
0
>>> max_(items)
4

View File

@ -0,0 +1,48 @@
>>> def most_significant(value):
... while value >= 10:
... value //= 10
... return value
>>> most_significant(12345)
1
>>> most_significant(99)
9
>>> most_significant(0)
0
------------------------------------------------------------------------------
>>> def add(collection, key, value):
... index = most_significant(key)
... collection[index].append((key, value))
>>> def contains(collection, key):
... index = most_significant(key)
... for k, v in collection[index]:
... if k == key:
... return True
... return False
# Create the collection of 10 lists
>>> collection = [[], [], [], [], [], [], [], [], [], []]
# Add some items, using key/value pairs
>>> add(collection, 123, 'a')
>>> add(collection, 456, 'b')
>>> add(collection, 789, 'c')
>>> add(collection, 101, 'c')
# Look at the collection
>>> collection
[[], [(123, 'a'), (101, 'c')], [], [],
[(456, 'b')], [], [], [(789, 'c')], [], []]
# Check if the contains works correctly
>>> contains(collection, 123)
True
>>> contains(collection, 1)
False

View File

@ -0,0 +1,38 @@
# All output in the table below is generated using this function
>>> def print_set(expression, set_):
... 'Print set as a string sorted by letters'
... print(expression, ''.join(sorted(set_)))
>>> spam = set('spam')
>>> print_set('spam:', spam)
spam: amps
>>> eggs = set('eggs')
>>> print_set('eggs:', eggs)
eggs: egs
------------------------------------------------------------------------------
>>> current_users = set((
... 'a',
... 'b',
... 'd',
... ))
>>> new_users = set((
... 'b',
... 'c',
... 'd',
... 'e',
... ))
>>> to_insert = new_users - current_users
>>> sorted(to_insert)
['c', 'e']
>>> to_delete = current_users - new_users
>>> sorted(to_delete)
['a']
>>> unchanged = new_users & current_users
>>> sorted(unchanged)
['b', 'd']

View File

@ -0,0 +1,99 @@
>>> spam = 1, 2, 3
>>> eggs = 4, 5, 6
>>> data = dict()
>>> data[spam] = 'spam'
>>> data[eggs] = 'eggs'
>>> import pprint # Using pprint for consistent and sorted output
>>> pprint.pprint(data)
{(1, 2, 3): 'spam', (4, 5, 6): 'eggs'}
------------------------------------------------------------------------------
>>> spam = 1, 'abc', (2, 3, (4, 5)), 'def'
>>> eggs = 4, (spam, 5), 6
>>> data = dict()
>>> data[spam] = 'spam'
>>> data[eggs] = 'eggs'
>>> import pprint # Using pprint for consistent and sorted output
>>> pprint.pprint(data)
{(1, 'abc', (2, 3, (4, 5)), 'def'): 'spam',
(4, ((1, 'abc', (2, 3, (4, 5)), 'def'), 5), 6): 'eggs'}
------------------------------------------------------------------------------
# Assign using tuples on both sides
>>> a, b, c = 1, 2, 3
>>> a
1
# Assign a tuple to a single variable
>>> spam = a, (b, c)
>>> spam
(1, (2, 3))
# Unpack a tuple to two variables
>>> a, b = spam
>>> a
1
>>> b
(2, 3)
------------------------------------------------------------------------------
# Unpack with variable length objects which assigns a list instead
of a tuple
>>> spam, *eggs = 1, 2, 3, 4
>>> spam
1
>>> eggs
[2, 3, 4]
# Which can be unpacked as well of course
>>> a, b, c = eggs
>>> c
4
# This works for ranges as well
>>> spam, *eggs = range(10)
>>> spam
0
>>> eggs
[1, 2, 3, 4, 5, 6, 7, 8, 9]
# And it works both ways
>>> a, b, *c = a, *eggs
>>> a, b
(2, 1)
>>> c
[2, 3, 4, 5, 6, 7, 8, 9]
------------------------------------------------------------------------------
>>> def eggs(*args):
... print('args:', args)
>>> eggs(1, 2, 3)
args: (1, 2, 3)
------------------------------------------------------------------------------
>>> def spam_eggs():
... return 'spam', 'eggs'
>>> spam, eggs = spam_eggs()
>>> spam
'spam'
>>> eggs
'eggs'

View File

@ -0,0 +1,59 @@
>>> spam: int
>>> __annotations__['spam']
<class 'int'>
>>> spam = 'not a number'
>>> __annotations__['spam']
<class 'int'>
>>> import dataclasses
>>> @dataclasses.dataclass
... class Sandwich:
... spam: int
... eggs: int = 3
>>> Sandwich(1, 2)
Sandwich(spam=1, eggs=2)
>>> sandwich = Sandwich(4)
>>> sandwich
Sandwich(spam=4, eggs=3)
>>> sandwich.eggs
3
>>> dataclasses.asdict(sandwich)
{'spam': 4, 'eggs': 3}
>>> dataclasses.astuple(sandwich)
(4, 3)
>>> help(dataclasses.dataclass)
Help on ... dataclass(..., *, init=True, repr=True, eq=True, ...
>>> def __init__(self, spam, eggs=3):
... self.spam = spam
... self.eggs = eggs
>>> import typing
>>> @dataclasses.dataclass
... class Group:
... name: str
... parent: 'Group' = None
>>> @dataclasses.dataclass
... class User:
... username: str
... email: str = None
... groups: typing.List[Group] = None
>>> users = Group('users')
>>> admins = Group('admins', users)
>>> rick = User('rick', groups=[admins])
>>> gvr = User('gvanrossum', 'guido@python.org', [admins])
>>> rick.groups
[Group(name='admins', parent=Group(name='users', parent=None))]
>>> rick.groups[0].parent
Group(name='users', parent=None)

View File

@ -0,0 +1,74 @@
>>> import builtins
>>> builtin_vars = vars(builtins)
>>> key = 'something to search for'
>>> if key in locals():
... value = locals()[key]
... elif key in globals():
... value = globals()[key]
... elif key in builtin_vars:
... value = builtin_vars[key]
... else:
... raise NameError(f'name {key!r} is not defined')
Traceback (most recent call last):
...
NameError: name 'something to search for' is not defined
##############################################################################
>>> mappings = locals(), globals(), vars(builtins)
>>> for mapping in mappings:
... if key in mapping:
... value = mapping[key]
... break
... else:
... raise NameError(f'name {key!r} is not defined')
Traceback (most recent call last):
...
NameError: name 'something to search for' is not defined
##############################################################################
>>> import collections
>>> mappings = collections.ChainMap(
... locals(), globals(), vars(builtins))
>>> mappings[key]
Traceback (most recent call last):
...
KeyError: 'something to search for'
##############################################################################
>>> import json
>>> import pathlib
>>> import argparse
>>> import collections
>>> DEFAULT = dict(verbosity=1)
>>> config_file = pathlib.Path('config.json')
>>> if config_file.exists():
... config = json.load(config_file.open())
... else:
... config = dict()
>>> parser = argparse.ArgumentParser()
>>> parser.add_argument('-v', '--verbose', action='count',
... dest='verbosity')
_CountAction(...)
>>> args, _ = parser.parse_known_args(args=['-v'])
>>> defined_args = {k: v for k, v in vars(args).items() if v}
>>> combined = collections.ChainMap(defined_args, config, DEFAULT)
>>> combined['verbosity']
1
>>> args, _ = parser.parse_known_args(['-vv'])
>>> defined_args = {k: v for k, v in vars(args).items() if v}
>>> combined = collections.ChainMap(defined_args, config, DEFAULT)
>>> combined['verbosity']
2

View File

@ -0,0 +1,93 @@
>>> nodes = [
... ('a', 'b'),
... ('a', 'c'),
... ('b', 'a'),
... ('b', 'd'),
... ('c', 'a'),
... ('d', 'a'),
... ('d', 'b'),
... ('d', 'c'),
... ]
------------------------------------------------------------------------------
>>> graph = dict()
>>> for from_, to in nodes:
... if from_ not in graph:
... graph[from_] = []
... graph[from_].append(to)
>>> import pprint
>>> pprint.pprint(graph)
{'a': ['b', 'c'],
'b': ['a', 'd'],
'c': ['a'],
'd': ['a', 'b', 'c']}
------------------------------------------------------------------------------
>>> import collections
>>> graph = collections.defaultdict(list)
>>> for from_, to in nodes:
... graph[from_].append(to)
>>> import pprint
>>> pprint.pprint(graph)
defaultdict(<class 'list'>,
{'a': ['b', 'c'],
'b': ['a', 'd'],
'c': ['a'],
'd': ['a', 'b', 'c']})
------------------------------------------------------------------------------
>>> counter = collections.defaultdict(int)
>>> counter['spam'] += 5
>>> counter
defaultdict(<class 'int'>, {'spam': 5})
------------------------------------------------------------------------------
>>> import collections
>>> def tree(): return collections.defaultdict(tree)
------------------------------------------------------------------------------
>>> import json
>>> import collections
>>> def tree():
... return collections.defaultdict(tree)
>>> colours = tree()
>>> colours['other']['black'] = 0x000000
>>> colours['other']['white'] = 0xFFFFFF
>>> colours['primary']['red'] = 0xFF0000
>>> colours['primary']['green'] = 0x00FF00
>>> colours['primary']['blue'] = 0x0000FF
>>> colours['secondary']['yellow'] = 0xFFFF00
>>> colours['secondary']['aqua'] = 0x00FFFF
>>> colours['secondary']['fuchsia'] = 0xFF00FF
>>> print(json.dumps(colours, sort_keys=True, indent=4))
{
"other": {
"black": 0,
"white": 16777215
},
"primary": {
"blue": 255,
"green": 65280,
"red": 16711680
},
"secondary": {
"aqua": 65535,
"fuchsia": 16711935,
"yellow": 16776960
}
}

View File

@ -0,0 +1,60 @@
>>> import enum
>>> class Color(enum.Enum):
... red = 1
... green = 2
... blue = 3
>>> Color.red
<Color.red: 1>
>>> Color['red']
<Color.red: 1>
>>> Color(1)
<Color.red: 1>
>>> Color.red.name
'red'
>>> Color.red.value
1
>>> isinstance(Color.red, Color)
True
>>> Color.red is Color['red']
True
>>> Color.red is Color(1)
True
------------------------------------------------------------------------------
>>> for color in Color:
... color
<Color.red: 1>
<Color.green: 2>
<Color.blue: 3>
>>> colors = dict()
>>> colors[Color.green] = 0x00FF00
>>> colors
{<Color.green: 2>: 65280}
------------------------------------------------------------------------------
>>> import enum
>>> class Spam(enum.Enum):
... EGGS = 'eggs'
>>> Spam.EGGS == 'eggs'
False
------------------------------------------------------------------------------
>>> import enum
>>> class Spam(str, enum.Enum):
... EGGS = 'eggs'
>>> Spam.EGGS == 'eggs'
True

View File

@ -0,0 +1,29 @@
>>> import heapq
>>> heap = [1, 3, 5, 7, 2, 4, 3]
>>> heapq.heapify(heap)
>>> heap
[1, 2, 3, 7, 3, 4, 5]
>>> while heap:
... heapq.heappop(heap), heap
(1, [2, 3, 3, 7, 5, 4])
(2, [3, 3, 4, 7, 5])
(3, [3, 5, 4, 7])
(3, [4, 5, 7])
(4, [5, 7])
(5, [7])
(7, [])
>>> def heapsort(iterable):
... heap = []
... for value in iterable:
... heapq.heappush(heap, value)
...
... while heap:
... yield heapq.heappop(heap)
>>> list(heapsort([1, 3, 5, 2, 4, 1]))
[1, 1, 2, 3, 4, 5]

View File

@ -0,0 +1,99 @@
>>> import bisect
Using the regular sort:
>>> sorted_list = []
>>> sorted_list.append(5) # O(1)
>>> sorted_list.append(3) # O(1)
>>> sorted_list.append(1) # O(1)
>>> sorted_list.append(2) # O(1)
>>> sorted_list.sort() # O(n * log(n)) = 4 * log(4) = 8
>>> sorted_list
[1, 2, 3, 5]
Using bisect:
>>> sorted_list = []
>>> bisect.insort(sorted_list, 5) # O(n) = 1
>>> bisect.insort(sorted_list, 3) # O(n) = 2
>>> bisect.insort(sorted_list, 1) # O(n) = 3
>>> bisect.insort(sorted_list, 2) # O(n) = 4
>>> sorted_list
[1, 2, 3, 5]
------------------------------------------------------------------------------
>>> sorted_list = [1, 2, 5]
>>> def contains(sorted_list, value):
... for item in sorted_list:
... if item > value:
... break
... elif item == value:
... return True
... return False
>>> contains(sorted_list, 2) # Need to walk through 2 items, O(n) = 2
True
>>> contains(sorted_list, 4) # Need to walk through n items, O(n) = 3
False
>>> contains(sorted_list, 6) # Need to walk through n items, O(n) = 3
False
>>> import bisect
>>> sorted_list = [1, 2, 5]
>>> def contains(sorted_list, value):
... i = bisect.bisect_left(sorted_list, value)
... return i < len(sorted_list) and sorted_list[i] == value
>>> contains(sorted_list, 2) # Found it the first step, O(log(n)) = 1
True
>>> contains(sorted_list, 4) # No result after 2 steps, O(log(n)) = 2
False
>>> contains(sorted_list, 6) # No result after 2 steps, O(log(n)) = 2
False
>>> import bisect
>>> import collections
>>> class SortedList:
... def __init__(self, *values):
... self._list = sorted(values)
...
... def index(self, value):
... i = bisect.bisect_left(self._list, value)
... if i < len(self._list) and self._list[i] == value:
... return index
...
... def delete(self, value):
... del self._list[self.index(value)]
...
... def add(self, value):
... bisect.insort(self._list, value)
...
... def __iter__(self):
... for value in self._list:
... yield value
...
... def __exists__(self, value):
... return self.index(value) is not None
>>> sorted_list = SortedList(1, 3, 6, 2)
>>> 3 in sorted_list
True
>>> 5 in sorted_list
False
>>> sorted_list.add(5)
>>> 5 in sorted_list
True
>>> list(sorted_list)
[1, 2, 3, 5, 6]

View File

@ -0,0 +1,37 @@
>>> class Borg:
... _state = {}
... def __init__(self):
... self.__dict__ = self._state
>>> class SubBorg(Borg):
... pass
>>> a = Borg()
>>> b = Borg()
>>> c = Borg()
>>> a.a_property = 123
>>> b.a_property
123
>>> c.a_property
123
>>> class Singleton:
... def __new__(cls):
... if not hasattr(cls, '_instance'):
... cls._instance = super(Singleton, cls).__new__(cls)
...
... return cls._instance
>>> class SubSingleton(Singleton):
... pass
>>> a = Singleton()
>>> b = Singleton()
>>> c = SubSingleton()
>>> a.a_property = 123
>>> b.a_property
123
>>> c.a_property
123

View File

@ -0,0 +1,23 @@
>>> class Sandwich:
...
... def __init__(self, spam):
... self.spam = spam
...
... @property
... def spam(self):
... return self._spam
...
... @spam.setter
... def spam(self, value):
... self._spam = value
... if self._spam >= 5:
... print('You must be hungry')
...
... @spam.deleter
... def spam(self):
... self._spam = 0
>>> sandwich = Sandwich(2)
>>> sandwich.spam += 1
>>> sandwich.spam += 2
You must be hungry

View File

@ -0,0 +1,22 @@
>>> a = dict(x=1, y=2)
>>> b = dict(y=1, z=2)
>>> c = a.copy()
>>> c
{'x': 1, 'y': 2}
>>> c.update(b)
>>> a
{'x': 1, 'y': 2}
>>> b
{'y': 1, 'z': 2}
>>> c
{'x': 1, 'y': 1, 'z': 2}
------------------------------------------------------------------------------
>>> a = dict(x=1, y=2)
>>> b = dict(y=1, z=2)
>>> a | b
{'x': 1, 'y': 1, 'z': 2}

View File

View File

@ -0,0 +1,4 @@
Chapter 4, Functional Programming
##############################################################################
| Readability versus Brevity covers the functional programming techniques such as list/dict/set comprehensions and lambda statements that are available in Python. Additionally, it illustrates the similarities to the mathematical principles involved.

View File

@ -0,0 +1,17 @@
>>> def add_value_functional(items, value):
... return items + [value]
>>> items = [1, 2, 3]
>>> add_value_functional(items, 5)
[1, 2, 3, 5]
>>> items
[1, 2, 3]
>>> def add_value_regular(items, value):
... items.append(value)
... return items
>>> add_value_regular(items, 5)
[1, 2, 3, 5]
>>> items
[1, 2, 3, 5]

View File

@ -0,0 +1,129 @@
>>> squares = [x ** 2 for x in range(10)]
>>> squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
------------------------------------------------------------------------------
>>> odd_squares = [x ** 2 for x in range(10) if x % 2]
>>> odd_squares
[1, 9, 25, 49, 81]
------------------------------------------------------------------------------
>>> def square(x):
... return x ** 2
>>> def odd(x):
... return x % 2
>>> squares = list(map(square, range(10)))
>>> squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> odd_squares = list(filter(odd, map(square, range(10))))
>>> odd_squares
[1, 9, 25, 49, 81]
------------------------------------------------------------------------------
>>> import os
>>> directories = filter(os.path.isdir, os.listdir('.'))
# Versus:
>>> directories = [x for x in os.listdir('.') if os.path.isdir(x)]
------------------------------------------------------------------------------
>>> odd_squares = []
>>> for x in range(10):
... if x % 2:
... odd_squares.append(x ** 2)
>>> odd_squares
[1, 9, 25, 49, 81]
------------------------------------------------------------------------------
# List comprehension
>>> [x // 2 for x in range(3)]
[0, 0, 1]
# Set comprehension
>>> numbers = {x // 2 for x in range(3)}
>>> sorted(numbers)
[0, 1]
------------------------------------------------------------------------------
>>> import random
>>> [random.random() for _ in range(10) if random.random() >= 0.5]
... # doctest: +SKIP
[0.5211948104577864, 0.650010512129705, 0.021427316545174158]
------------------------------------------------------------------------------
>>> import random
>>> numbers = [random.random() for _ in range(10)] # doctest: +SKIP
>>> [x for x in numbers if x >= 0.5] # doctest: +SKIP
[0.715510247827078, 0.8426277505519564, 0.5071133900377911]
------------------------------------------------------------------------------
>>> import random
>>> [x for x in [random.random() for _ in range(10)] if x >= 0.5]
... # doctest: +SKIP
------------------------------------------------------------------------------
>>> import random
>>> [x for _ in range(10) for x in [random.random()] if x >= 0.5]
... # doctest: +SKIP
------------------------------------------------------------------------------
>>> [(x, y) for x in range(3) for y in range(3, 5)]
[(0, 3), (0, 4), (1, 3), (1, 4), (2, 3), (2, 4)]
------------------------------------------------------------------------------
>>> results = []
>>> for x in range(3):
... for y in range(3, 5):
... results.append((x, y))
...
>>> results
[(0, 3), (0, 4), (1, 3), (1, 4), (2, 3), (2, 4)]
------------------------------------------------------------------------------
>>> matrix = [
... [1, 2, 3, 4],
... [5, 6, 7, 8],
... [9, 10, 11, 12],
... ]
>>> reshaped_matrix = [
... [
... [y for x in matrix for y in x][i * len(matrix) + j]
... for j in range(len(matrix))
... ]
... for i in range(len(matrix[0]))
... ]
>>> import pprint
>>> pprint.pprint(reshaped_matrix, width=40)
[[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
[10, 11, 12]]

View File

@ -0,0 +1,10 @@
>>> {x: x ** 2 for x in range(6)}
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
>>> {x: x ** 2 for x in range(6) if x % 2}
{1: 1, 3: 9, 5: 25}
------------------------------------------------------------------------------
>>> {x ** 2: [y for y in range(x)] for x in range(5)}
{0: [], 1: [0], 4: [0, 1], 9: [0, 1, 2], 16: [0, 1, 2, 3]}

View File

@ -0,0 +1,5 @@
>>> [x*y for x in range(3) for y in range(3)]
[0, 0, 0, 0, 1, 2, 0, 2, 4]
>>> {x*y for x in range(3) for y in range(3)}
{0, 1, 2, 4}

View File

@ -0,0 +1,25 @@
>>> import operator
>>> values = dict(one=1, two=2, three=3)
>>> sorted(values.items())
[('one', 1), ('three', 3), ('two', 2)]
>>> sorted(values.items(), key=lambda item: item[1])
[('one', 1), ('two', 2), ('three', 3)]
>>> get_value = operator.itemgetter(1)
>>> sorted(values.items(), key=get_value)
[('one', 1), ('two', 2), ('three', 3)]
------------------------------------------------------------------------------
>>> key = lambda item: item[1]
>>> def key(item):
... return item[1]
------------------------------------------------------------------------------
>>> def key(spam): return spam.value
>>> key = lambda spam: spam.value

View File

@ -0,0 +1,54 @@
::
Y = lambda f: lambda *args: f(Y(f))(*args)
------------------------------------------------------------------------------
::
def Y(f):
def y(*args):
y_function = f(Y(f))
return y_function(*args)
return y
------------------------------------------------------------------------------
>>> Y = lambda f: lambda *args: f(Y(f))(*args)
>>> def factorial(combinator):
... def _factorial(n):
... if n:
... return n * combinator(n - 1)
... else:
... return 1
... return _factorial
>>> Y(factorial)(5)
120
------------------------------------------------------------------------------
>>> Y = lambda f: lambda *args: f(Y(f))(*args)
>>> Y(lambda c: lambda n: n and n * c(n - 1) or 1)(5)
120
------------------------------------------------------------------------------
>>> Y = lambda f: lambda *args: f(Y(f))(*args)
>>> Y(lambda c: lambda n: n * c(n - 1) if n else 1)(5)
120
------------------------------------------------------------------------------
>>> quicksort = Y(lambda f:
... lambda x: (
... f([item for item in x if item < x[0]])
... + [y for y in x if x[0] == y]
... + f([item for item in x if item > x[0]])
... ) if x else [])
>>> quicksort([1, 3, 5, 4, 1, 3, 2])
[1, 1, 2, 3, 3, 4, 5]

View File

@ -0,0 +1,50 @@
>>> import heapq
>>> heap = []
>>> heapq.heappush(heap, 1)
>>> heapq.heappush(heap, 3)
>>> heapq.heappush(heap, 5)
>>> heapq.heappush(heap, 2)
>>> heapq.heappush(heap, 4)
>>> heapq.nsmallest(3, heap)
[1, 2, 3]
------------------------------------------------------------------
>>> def push(*args, **kwargs):
... return heapq.heappush(heap, *args, **kwargs)
------------------------------------------------------------------
>>> import functools
>>> import heapq
>>> heap = []
>>> push = functools.partial(heapq.heappush, heap)
>>> smallest = functools.partial(heapq.nsmallest, iterable=heap)
>>> push(1)
>>> push(3)
>>> push(5)
>>> push(2)
>>> push(4)
>>> smallest(3)
[1, 2, 3]
------------------------------------------------------------------
>>> lambda_push = lambda x: heapq.heappush(heap, x)
>>> heapq.heappush
<built-in function heappush>
>>> push
functools.partial(<built-in function heappush>, [1, 2, 5, 3, 4])
>>> lambda_push
<function <lambda> at ...>
>>> heapq.heappush.__doc__
'Push item onto heap, maintaining the heap invariant.'
>>> push.__doc__
'partial(func, *args, **keywords) - new function ...'
>>> lambda_push.__doc__

View File

@ -0,0 +1,86 @@
>>> import operator
>>> import functools
>>> functools.reduce(operator.mul, range(1, 5))
24
------------------------------------------------------------------------------
>>> from operator import mul
>>> mul(mul(mul(1, 2), 3), 4)
24
------------------------------------------------------------------------------
>>> import operator
>>> def reduce(function, iterable):
... print(f'iterable={iterable}')
... # Fetch the first item to prime `result`
... result, *iterable = iterable
...
... for item in iterable:
... old_result = result
... result = function(result, item)
... print(f'{old_result} * {item} = {result}')
...
... return result
>>> iterable = list(range(1, 5))
>>> iterable
[1, 2, 3, 4]
>>> reduce(operator.mul, iterable)
iterable=[1, 2, 3, 4]
1 * 2 = 2
2 * 3 = 6
6 * 4 = 24
24
------------------------------------------------------------------------------
>>> import operator
>>> iterable = range(1, 5)
# The initial values:
>>> a, b, *iterable = iterable
>>> a, b, iterable
(1, 2, [3, 4])
# First run
>>> a = operator.mul(a, b)
>>> b, *iterable = iterable
>>> a, b, iterable
(2, 3, [4])
# Second run
>>> a = operator.mul(a, b)
>>> b, *iterable = iterable
>>> a, b, iterable
(6, 4, [])
# Third and last run
>>> a = operator.mul (a, b)
>>> a
24
------------------------------------------------------------------------------
>>> import operator
>>> import collections
>>> iterable = collections.deque(range(1, 5))
>>> value = iterable.popleft()
>>> while iterable:
... value = operator.mul(value, iterable.popleft())
>>> value
24

View File

@ -0,0 +1,73 @@
>>> import json
>>> import functools
>>> import collections
>>> def tree():
... return collections.defaultdict(tree)
# Build the tree:
>>> taxonomy = tree()
>>> reptilia = taxonomy['Chordata']['Vertebrata']['Reptilia']
>>> reptilia['Squamata']['Serpentes']['Pythonidae'] = [
... 'Liasis', 'Morelia', 'Python']
# The actual contents of the tree
>>> print(json.dumps(taxonomy, indent=4))
{
"Chordata": {
"Vertebrata": {
"Reptilia": {
"Squamata": {
"Serpentes": {
"Pythonidae": [
"Liasis",
"Morelia",
"Python"
]
}
}
}
}
}
}
# Let's build the lookup function
>>> import operator
>>> def lookup(tree, path):
... # Split the path for easier access
... path = path.split('.')
...
... # Use `operator.getitem(a, b)` to get `a[b]`
... # And use reduce to recursively fetch the items
... return functools.reduce(operator.getitem, path, tree)
>>> path = 'Chordata.Vertebrata.Reptilia.Squamata.Serpentes'
>>> dict(lookup(taxonomy, path))
{'Pythonidae': ['Liasis', 'Morelia', 'Python']}
# The path we wish to get
>>> path = 'Chordata.Vertebrata.Reptilia.Squamata'
>>> lookup(taxonomy, path).keys()
dict_keys(['Serpentes'])
------------------------------------------------------------------------------
>>> fold_left = lambda iterable, initializer=None: functools.reduce(
... lambda x, y: function(x, y),
... iterable,
... initializer,
... )
>>> fold_right = lambda iterable, initializer=None: functools.reduce(
... lambda x, y: function(y, x),
... reversed(iterable),
... initializer,
... )

View File

@ -0,0 +1,9 @@
>>> import operator
>>> import itertools
# Sales per month
>>> months = [10, 8, 5, 7, 12, 10, 5, 8, 15, 3, 4, 2]
>>> list(itertools.accumulate(months, operator.add))
[10, 18, 23, 30, 42, 52, 57, 65, 80, 83, 87, 89]

View File

@ -0,0 +1,13 @@
>>> import itertools
>>> a = range(3)
>>> b = range(5)
>>> list(itertools.chain(a, b))
[0, 1, 2, 0, 1, 2, 3, 4]
>>> import itertools
>>> iterables = [range(3), range(5)]
>>> list(itertools.chain.from_iterable(iterables))
[0, 1, 2, 0, 1, 2, 3, 4]

View File

@ -0,0 +1,24 @@
>>> import itertools
>>> list(itertools.compress(range(1000), [0, 1, 1, 1, 0, 1]))
[1, 2, 3, 5]
>>> primes = [0, 0, 1, 1, 0, 1, 0, 1]
>>> odd = [0, 1, 0, 1, 0, 1, 0, 1]
>>> numbers = ['zero', 'one', 'two', 'three', 'four', 'five']
# Primes:
>>> list(itertools.compress(numbers, primes))
['two', 'three', 'five']
# Odd numbers
>>> list(itertools.compress(numbers, odd))
['one', 'three', 'five']
# Odd primes
>>> list(itertools.compress(numbers, map(all, zip(odd, primes))))
['three', 'five']

View File

@ -0,0 +1,9 @@
>>> import itertools
>>> list(itertools.dropwhile(lambda x: x <= 3, [1, 3, 5, 4, 2]))
[5, 4, 2]
>>> import itertools
>>> list(itertools.takewhile(lambda x: x <= 3, [1, 3, 5, 4, 2]))
[1, 3]

View File

@ -0,0 +1,11 @@
>>> import itertools
>>> list(itertools.islice(itertools.count(), 10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> list(itertools.islice(itertools.count(), 5, 10, 2))
[5, 7, 9]
>>> list(itertools.islice(itertools.count(10, 2.5), 5))
[10, 12.5, 15.0, 17.5, 20.0]

View File

@ -0,0 +1,34 @@
>>> import operator
>>> import itertools
>>> words = ['aa', 'ab', 'ba', 'bb', 'ca', 'cb', 'cc']
# Gets the first element from the iterable
>>> getter = operator.itemgetter(0)
>>> for group, items in itertools.groupby(words, key=getter):
... print(f'group: {group}, items: {list(items)}')
group: a, items: ['aa', 'ab']
group: b, items: ['ba', 'bb']
group: c, items: ['ca', 'cb', 'cc']
------------------------------------------------------------
>>> import operator
>>> import itertools
>>> words = ['aa', 'bb', 'ca', 'ab', 'ba', 'cb', 'cc']
# Gets the first element from the iterable
>>> getter = operator.itemgetter(0)
>>> for group, items in itertools.groupby(words, key=getter):
... print(f'group: {group}, items: {list(items)}')
group: a, items: ['aa']
group: b, items: ['bb']
group: c, items: ['ca']
group: a, items: ['ab']
group: b, items: ['ba']
group: c, items: ['cb', 'cc']

View File

View File

@ -0,0 +1,4 @@
Chapter 5, Decorators
##############################################################################
| Enabling Code Reuse by Decorating explains not only how to create your own function/class decorators but also how internal decorators such as property, staticmethod and classmethod function.

View File

@ -0,0 +1,39 @@
>>> def decorator(function):
... return function
>>> def add(a, b):
... return a + b
>>> add = decorator(add)
>>> @decorator
... def add(a, b):
... return a + b
>>> import functools
>>> def decorator(function):
... # This decorator makes sure we mimic the wrapped function
... @functools.wraps(function)
... def _decorator(a, b):
... # Pass the modified arguments to the function
... result = function(a, b + 5)
...
... # Logging the function call
... name = function.__name__
... print(f'{name}(a={a}, b={b}): {result}')
...
... # Return a modified result
... return result + 4
...
... return _decorator
>>> @decorator
... def func(a, b):
... return a + b
>>> func(1, 2)
func(a=1, b=2): 8
12

View File

@ -0,0 +1,82 @@
>>> import functools
>>> def decorator(function):
... @functools.wraps(function)
... def _decorator(*args, **kwargs):
... a, b = args
... return function(a, b + 5)
...
... return _decorator
>>> @decorator
... def func(a, b):
... return a + b
>>> func(1, 2)
8
>>> func(a=1, b=2)
Traceback (most recent call last):
...
ValueError: not enough values to unpack (expected 2, got 0)
>>> def add(a, b, /):
... return a + b
>>> add(a=1, b=2)
Traceback (most recent call last):
...
TypeError: add() got some positional-only arguments passed ...
>>> def add(*, a, b):
... return a + b
>>> add(1, 2)
Traceback (most recent call last):
...
TypeError: add() takes 0 positional arguments but 2 were given
>>> import inspect
>>> import functools
>>> def decorator(function):
... # Use the inspect module to get function signature. More
... # about this in the logging chapter
... signature = inspect.signature(function)
...
... @functools.wraps(function)
... def _decorator(*args, **kwargs):
... # Bind the arguments to the given *args and **kwargs.
... # If you want to make arguments optional use
... # signature.bind_partial instead.
... bound = signature.bind(*args, **kwargs)
...
... # Apply the defaults so b is always filled
... bound.apply_defaults()
...
... # Extract the filled arguments. If the amount of
... # arguments is still expected to be fixed you can use
... # tuple unpacking: `a, b = bound.arguments.values()`
... a = bound.arguments['a']
... b = bound.arguments['b']
... return function(a, b + 5)
...
... return _decorator
>>> @decorator
... def func(a, b=3):
... return a + b
>>> func(1, 2)
8
>>> func(a=1, b=2)
8
>>> func(a=1)
9

View File

@ -0,0 +1,43 @@
>>> def decorator(function):
... def _decorator(*args, **kwargs):
... return function(*args, **kwargs)
... return _decorator
>>> @decorator
... def add(a, b):
... '''Add a and b'''
... return a + b
>>> help(add)
Help on function _decorator in module ...:
<BLANKLINE>
_decorator(*args, **kwargs)
<BLANKLINE>
>>> add.__name__
'_decorator'
------------------------------------------------------------------------------
>>> import functools
>>> def decorator(function):
... @functools.wraps(function)
... def _decorator(*args, **kwargs):
... return function(*args, **kwargs)
... return _decorator
>>> @decorator
... def add(a, b):
... '''Add a and b'''
... return a + b
>>> help(add)
Help on function add in module ...:
<BLANKLINE>
add(a, b)
Add a and b
<BLANKLINE>
>>> add.__name__
'add'

View File

@ -0,0 +1,30 @@
>>> import functools
>>> def track(function=None, label=None):
... # Trick to add an optional argument to our decorator
... if label and not function:
... return functools.partial(track, label=label)
...
... print(f'initializing {label}')
...
... @functools.wraps(function)
... def _track(*args, **kwargs):
... print(f'calling {label}')
... function(*args, **kwargs)
... print(f'called {label}')
...
... return _track
>>> @track(label='outer')
... @track(label='inner')
... def func():
... print('func')
initializing inner
initializing outer
>>> func()
calling outer
calling inner
func
called inner
called outer

View File

@ -0,0 +1,35 @@
>>> import collections
>>> class EventRegistry:
... def __init__(self):
... self.registry = collections.defaultdict(list)
...
... def on(self, *events):
... def _on(function):
... for event in events:
... self.registry[event].append(function)
... return function
...
... return _on
...
... def fire(self, event, *args, **kwargs):
... for function in self.registry[event]:
... function(*args, **kwargs)
>>> events = EventRegistry()
>>> @events.on('success', 'error')
... def teardown(value):
... print(f'Tearing down got: {value}')
>>> @events.on('success')
... def success(value):
... print(f'Successfully executed: {value}')
>>> events.fire('non-existing', 'nothing to see here')
>>> events.fire('error', 'Oops, some error here')
Tearing down got: Oops, some error here
>>> events.fire('success', 'Everything is fine')
Tearing down got: Everything is fine
Successfully executed: Everything is fine

View File

@ -0,0 +1,88 @@
>>> import functools
>>> def memoize(function):
... # Store the cache as attribute of the function so we can
... # apply the decorator to multiple functions without
... # sharing the cache.
... function.cache = dict()
...
... @functools.wraps(function)
... def _memoize(*args):
... # If the cache is not available, call the function
... # Note that all args need to be hashable
... if args not in function.cache:
... function.cache[args] = function(*args)
... return function.cache[args]
... return _memoize
>>> @memoize
... def fibonacci(n):
... if n < 2:
... return n
... else:
... return fibonacci(n - 1) + fibonacci(n - 2)
>>> for i in range(1, 7):
... print(f'fibonacci {i}: {fibonacci(i)}')
fibonacci 1: 1
fibonacci 2: 1
fibonacci 3: 2
fibonacci 4: 3
fibonacci 5: 5
fibonacci 6: 8
>>> fibonacci.__wrapped__.cache
{(1,): 1, (0,): 0, (2,): 1, (3,): 2, (4,): 3, (5,): 5, (6,): 8}
# It breaks keyword arguments:
>>> fibonacci(n=2)
Traceback (most recent call last):
...
TypeError: ... got an unexpected keyword argument 'n'
# Unhashable types don't work as dict keys:
>>> fibonacci([123])
Traceback (most recent call last):
...
TypeError: unhashable type: 'list'
------------------------------------------------------------------------------
>>> import functools
# Create a simple call counting decorator
>>> def counter(function):
... function.calls = 0
... @functools.wraps(function)
... def _counter(*args, **kwargs):
... function.calls += 1
... return function(*args, **kwargs)
... return _counter
# Create a LRU cache with size 3
>>> @functools.lru_cache(maxsize=3)
... @counter
... def fibonacci(n):
... if n < 2:
... return n
... else:
... return fibonacci(n - 1) + fibonacci(n - 2)
>>> fibonacci(100)
354224848179261915075
# The LRU cache offers some useful statistics
>>> fibonacci.cache_info()
CacheInfo(hits=98, misses=101, maxsize=3, currsize=3)
# The result from our counter function which is now wrapped both by
our counter and the cache
>>> fibonacci.__wrapped__.__wrapped__.calls
101

View File

@ -0,0 +1,36 @@
>>> import functools
>>> def add(function=None, add_n=0):
... # function is not callable so it's probably `add_n`
... if not callable(function):
... # Test to make sure we don't pass `None` as `add_n`
... if function is not None:
... add_n = function
... return functools.partial(add, add_n=add_n)
...
... @functools.wraps(function)
... def _add(n):
... return function(n) + add_n
...
... return _add
>>> @add
... def add_zero(n):
... return n
>>> @add(1)
... def add_one(n):
... return n
>>> @add(add_n=2)
... def add_two(n):
... return n
>>> add_zero(5)
5
>>> add_one(5)
6
>>> add_two(5)
7

View File

@ -0,0 +1,25 @@
>>> import functools
>>> class Debug(object):
...
... def __init__(self, function):
... self.function = function
... # functools.wraps for classes
... functools.update_wrapper(self, function)
...
... def __call__(self, *args, **kwargs):
... output = self.function(*args, **kwargs)
... name = self.function.__name__
... print(f'{name}({args!r}, {kwargs!r}): {output!r}')
... return output
>>> @Debug
... def add(a, b=0):
... return a + b
...
>>> output = add(3)
add((3,), {}): 3
>>> output = add(a=4, b=2)
add((), {'a': 4, 'b': 2}): 6

View File

@ -0,0 +1,21 @@
>>> import functools
>>> def plus_one(function):
... @functools.wraps(function)
... def _plus_one(self, n, *args):
... return function(self, n + 1, *args)
... return _plus_one
>>> class Adder(object):
... @plus_one
... def add(self, a, b=0):
... return a + b
>>> adder = Adder()
>>> adder.add(0)
1
>>> adder.add(3, 4)
8

View File

@ -0,0 +1,146 @@
>>> import pprint
>>> class Spam(object):
...
... def some_instancemethod(self, *args, **kwargs):
... pprint.pprint(locals(), width=60)
...
... @classmethod
... def some_classmethod(cls, *args, **kwargs):
... pprint.pprint(locals(), width=60)
...
... @staticmethod
... def some_staticmethod(*args, **kwargs):
... pprint.pprint(locals(), width=60)
# Create an instance so we can compare the difference between
executions with and without instances easily
>>> spam = Spam()
------------------------------------------------------------------------------
# With an instance (note the lowercase spam)
>>> spam.some_instancemethod(1, 2, a=3, b=4)
{'args': (1, 2),
'kwargs': {'a': 3, 'b': 4},
'self': <__main__.Spam object at ...>}
# Without an instance (note the capitalized Spam)
>>> Spam.some_instancemethod()
Traceback (most recent call last):
...
TypeError: ...some_instancemethod() missing ... argument: 'self'
# But what if we add parameters? Be very careful with these!
Our first argument is now used as an argument, this can give
very strange and unexpected errors
>>> Spam.some_instancemethod(1, 2, a=3, b=4)
{'args': (2,), 'kwargs': {'a': 3, 'b': 4}, 'self': 1}
------------------------------------------------------------------------------
# Classmethods are expectedly identical
>>> spam.some_classmethod(1, 2, a=3, b=4)
{'args': (1, 2),
'cls': <class '__main__.Spam'>,
'kwargs': {'a': 3, 'b': 4}}
>>> Spam.some_classmethod()
{'args': (), 'cls': <class '__main__.Spam'>, 'kwargs': {}}
>>> Spam.some_classmethod(1, 2, a=3, b=4)
{'args': (1, 2),
'cls': <class '__main__.Spam'>,
'kwargs': {'a': 3, 'b': 4}}
------------------------------------------------------------------------------
# Staticmethods are also identical
>>> spam.some_staticmethod(1, 2, a=3, b=4)
{'args': (1, 2), 'kwargs': {'a': 3, 'b': 4}}
>>> Spam.some_staticmethod()
{'args': (), 'kwargs': {}}
>>> Spam.some_staticmethod(1, 2, a=3, b=4)
{'args': (1, 2), 'kwargs': {'a': 3, 'b': 4}}
------------------------------------------------------------------------------
>>> class Spam:
...
... def __init__(self, spam=1):
... self.spam = spam
...
... def __get__(self, instance, cls):
... return self.spam + instance.eggs
...
... def __set__(self, instance, value):
... instance.eggs = value - self.spam
>>> class Sandwich:
...
... spam = Spam(5)
...
... def __init__(self, eggs):
... self.eggs = eggs
>>> sandwich = Sandwich(1)
>>> sandwich.eggs
1
>>> sandwich.spam
6
>>> sandwich.eggs = 10
>>> sandwich.spam
15
------------------------------------------------------------------------------
>>> import functools
>>> class ClassMethod(object):
... def __init__(self, method):
... self.method = method
...
... def __get__(self, instance, cls):
... @functools.wraps(self.method)
... def method(*args, **kwargs):
... return self.method(cls, *args, **kwargs)
... return method
>>> class StaticMethod(object):
... def __init__(self, method):
... self.method = method
...
... def __get__(self, instance, cls):
... return self.method
>>> class Sandwich:
... spam = 'class'
...
... def __init__(self, spam):
... self.spam = spam
...
... @ClassMethod
... def some_classmethod(cls, arg):
... return cls.spam, arg
...
... @StaticMethod
... def some_staticmethod(arg):
... return Sandwich.spam, arg
>>> sandwich = Sandwich('instance')
>>> sandwich.spam
'instance'
>>> sandwich.some_classmethod('argument')
('class', 'argument')
>>> sandwich.some_staticmethod('argument')
('class', 'argument')

View File

@ -0,0 +1,137 @@
>>> import functools
>>> class Sandwich:
... def get_eggs(self):
... print('getting eggs')
... return self._eggs
...
... def set_eggs(self, eggs):
... print('setting eggs to %s' % eggs)
... self._eggs = eggs
...
... def delete_eggs(self):
... print('deleting eggs')
... del self._eggs
...
... eggs = property(get_eggs, set_eggs, delete_eggs)
...
... @property
... def spam(self):
... print('getting spam')
... return self._spam
...
... @spam.setter
... def spam(self, spam):
... print('setting spam to %s' % spam)
... self._spam = spam
...
... @spam.deleter
... def spam(self):
... print('deleting spam')
... del self._spam
...
... @functools.cached_property
... def bacon(self):
... print('getting bacon')
... return 'bacon!'
>>> sandwich = Sandwich()
>>> sandwich.eggs = 123
setting eggs to 123
>>> sandwich.eggs
getting eggs
123
>>> del sandwich.eggs
deleting eggs
>>> sandwich.bacon
getting bacon
'bacon!'
>>> sandwich.bacon
'bacon!'
------------------------------------------------------------------------------
>>> class Property(object):
... def __init__(self, fget=None, fset=None, fdel=None):
... self.fget = fget
... self.fset = fset
... self.fdel = fdel
...
... def __get__(self, instance, cls):
... if instance is None:
... # Redirect class (not instance) properties to self
... return self
... elif self.fget:
... return self.fget(instance)
...
... def __set__(self, instance, value):
... self.fset(instance, value)
...
... def __delete__(self, instance):
... self.fdel(instance)
...
... def getter(self, fget):
... return Property(fget, self.fset, self.fdel)
...
... def setter(self, fset):
... return Property(self.fget, fset, self.fdel)
...
... def deleter(self, fdel):
... return Property(self.fget, self.fset, fdel)
>>> class Sandwich:
... @Property
... def eggs(self):
... return self._eggs
...
... @eggs.setter
... def eggs(self, value):
... self._eggs = value
...
... @eggs.deleter
... def eggs(self):
... del self._eggs
>>> sandwich = Sandwich()
>>> sandwich.eggs = 5
>>> sandwich.eggs
5
------------------------------------------------------------------------------
>>> class Sandwich(object):
... def __init__(self):
... self.registry = {}
...
... def __getattr__(self, key):
... print('Getting %r' % key)
... return self.registry.get(key, 'Undefined')
...
... def __setattr__(self, key, value):
... if key == 'registry':
... object.__setattr__(self, key, value)
... else:
... print('Setting %r to %r' % (key, value))
... self.registry[key] = value
...
... def __delattr__(self, key):
... print('Deleting %r' % key)
... del self.registry[key]
>>> sandwich = Sandwich()
>>> sandwich.a
Getting 'a'
'Undefined'
>>> sandwich.a = 1
Setting 'a' to 1
>>> sandwich.a
Getting 'a'
1
>>> del sandwich.a
Deleting 'a'

View File

@ -0,0 +1,26 @@
>>> import functools
>>> def singleton(cls):
... instances = dict()
... @functools.wraps(cls)
... def _singleton(*args, **kwargs):
... if cls not in instances:
... instances[cls] = cls(*args, **kwargs)
... return instances[cls]
... return _singleton
>>> @singleton
... class SomeSingleton(object):
... def __init__(self):
... print('Executing init')
>>> a = SomeSingleton()
Executing init
>>> b = SomeSingleton()
>>> a is b
True
>>> a.x = 123
>>> b.x
123

Some files were not shown because too many files have changed in this diff Show More