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
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
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
+5
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.
+53
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"},
]
@@ -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
+3
View File
@@ -0,0 +1,3 @@
Chapter 2, Interactive Python Shells
##############################################################################
@@ -0,0 +1,2 @@
>>> 'Hello world!'
'Hello world!'
@@ -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!
@@ -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))
@@ -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
@@ -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
>>>
@@ -0,0 +1,4 @@
with open('reload.txt', 'a+') as fh:
fh.write('x')
fh.seek(0)
print(fh.read())
+7
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()))
+1
View File
@@ -0,0 +1 @@
xxxxx
@@ -0,0 +1,3 @@
# coding: utf-8
sandwich = dict(spam=2, eggs=1, sausage=1)
+4
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.
@@ -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]'
+22
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!
@@ -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]
@@ -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
@@ -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]
@@ -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
@@ -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
@@ -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
@@ -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,
... )
@@ -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
+7
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')
@@ -0,0 +1,3 @@
>>> import warnings
>>> warnings.warn('Something deprecated', DeprecationWarning)
@@ -0,0 +1,9 @@
>>> from json import loads
>>> loads('{}')
{}
>>> import json
>>> json.loads('{}')
{}
@@ -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
@@ -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
+21
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)]
+18
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()
+2
View File
@@ -0,0 +1,2 @@
some_number: int
some_number = 'test'
+2
View File
@@ -0,0 +1,2 @@
def spam(a,b,c):print(a,b+c)
def eggs():pass
@@ -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
@@ -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
@@ -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']
@@ -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'
@@ -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
@@ -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
@@ -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,
@@ -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')
@@ -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,
@@ -0,0 +1,9 @@
import T_28_circular_imports_b
class FileA:
pass
class FileC(T_28_circular_imports_b.FileB):
pass
@@ -0,0 +1,5 @@
import T_28_circular_imports_a
class FileB(T_28_circular_imports_a.FileA):
pass
@@ -0,0 +1,2 @@
class FileA:
pass
@@ -0,0 +1,5 @@
import T_29_circular_imports_a
class FileB(T_29_circular_imports_a.FileA):
pass
@@ -0,0 +1,5 @@
import T_29_circular_imports_b
class FileC(T_29_circular_imports_b.FileB):
pass
@@ -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
+7
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()))
+4
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.
@@ -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
+89
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
+48
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
+38
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']
+99
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'
@@ -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)
+74
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
@@ -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
}
}
+60
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
+29
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]
+99
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]
@@ -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
@@ -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
+22
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
+4
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.
@@ -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]
@@ -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]]
@@ -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]}
@@ -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}
@@ -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
@@ -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]
@@ -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__
@@ -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
@@ -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,
... )
@@ -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]
@@ -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]
@@ -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']
@@ -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]
@@ -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]
@@ -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']
+4
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.
@@ -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
@@ -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
+43
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'
@@ -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
@@ -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
+88
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
@@ -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
@@ -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
@@ -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
@@ -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')
+137
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'
+26
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