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
+4
View File
@@ -0,0 +1,4 @@
Chapter 12, Performance
##############################################################################
| Tracking and Reducing your Memory and CPU Usage shows several methods of measuring and improving CPU and memory usage.
+60
View File
@@ -0,0 +1,60 @@
import timeit
def test_list():
return list(range(10000))
def test_list_comprehension():
return [i for i in range(10000)]
def test_append():
x = []
for i in range(10000):
x.append(i)
return x
def test_insert():
x = []
for i in range(10000):
x.insert(0, i)
return x
def benchmark(function, number=100, repeat=10):
# Measure the execution times. Passing the globals() is an
# easy way to make the functions available.
times = timeit.repeat(function, number=number,
globals=globals())
# The repeat function gives `repeat` results so we take the
# min() and divide it by the number of runs
time = min(times) / number
print(f'{number} loops, best of {repeat}: {time:9.6f}s :: ',
function.__name__)
def autorange_benchmark(function):
def print_result(number, time_taken):
# The autorange function keeps trying until the total
# runtime (time_taken) reaches 0.2 seconds. To get the
# time per run we need to divide it by the number of runs
time = time_taken / number
name = function.__name__
print(f'{number} loops, average: {time:9.6f}s :: {name}')
# Measure the execution times. Passing the globals() is an
# easy way to make the functions available.
timer = timeit.Timer(function, globals=globals())
timer.autorange(print_result)
if __name__ == '__main__':
benchmark(test_list)
benchmark(test_list_comprehension)
benchmark(test_append)
benchmark(test_insert)
+3
View File
@@ -0,0 +1,3 @@
import timeit
timeit.main(args=['[x for x in range(1000000)]'])
+56
View File
@@ -0,0 +1,56 @@
import gc
import time
import functools
assert time
TIMEIT_TEMPLATE = '''
def run(number):
{setup}
start = time.perf_counter()
for i in range(number):
{statement}
stop = time.perf_counter()
return stop - start
'''
def timeit(statement, setup='', number=1000000, globals_=None):
# Get or create globals
globals_ = globals() if globals_ is None else globals_
# Create the test code so we can separate the namespace
src = TIMEIT_TEMPLATE.format(
statement=statement,
setup=setup,
number=number,
)
# Compile the source
code = compile(src, '<source>', 'exec')
# Define locals for the benchmarked code
locals_ = {}
# Execute the code so we can get the benchmark fuction
exec(code, globals_, locals_)
# Get the run function from locals() which was added by `exec`
run = functools.partial(locals_['run'], number=number)
# Disable garbage collection to prevent skewing results
gc.disable()
try:
result = run()
finally:
gc.enable()
return result
if __name__ == '__main__':
statement = '[x for x in range(100)]'
print('{:.7f}'.format(timeit(statement, number=1)))
print('{:.7f}'.format(timeit(statement) / 1000000))
print('{:.7f}'.format(timeit(statement, number=1)))
print('{:.7f}'.format(timeit(statement) / 1000000))
@@ -0,0 +1,26 @@
import sys
import functools
@functools.lru_cache()
def fibonacci_cached(n):
if n < 2:
return n
else:
return fibonacci_cached(n - 1) + fibonacci_cached(n - 2)
def fibonacci(n):
if n < 2:
return n
else:
return fibonacci(n - 1) + fibonacci(n - 2)
if __name__ == '__main__':
n = 30
if sys.argv[-1] == 'cache':
fibonacci_cached(n)
else:
fibonacci(n)
@@ -0,0 +1,21 @@
import profile
if __name__ == '__main__':
profiler = profile.Profile()
for i in range(10):
print(profiler.calibrate(100000))
##############################################################################
import profile
# The number here is bias calculated earlier
profile.Profile.bias = 9.809351906482531e-07
##############################################################################
import profile
profiler = profile.Profile(bias=9.809351906482531e-07)
@@ -0,0 +1,41 @@
import sys
import pstats
import profile
import functools
@functools.lru_cache()
def fibonacci_cached(n):
if n < 2:
return n
else:
return fibonacci_cached(n - 1) + fibonacci_cached(n - 2)
def fibonacci(n):
if n < 2:
return n
else:
return fibonacci(n - 1) + fibonacci(n - 2)
if __name__ == '__main__':
profiler = profile.Profile(bias=9.809351906482531e-07)
n = 30
if sys.argv[-1] == 'cache':
profiler.runcall(fibonacci_cached, n)
else:
profiler.runcall(fibonacci, n)
stats = pstats.Stats(profiler).sort_stats('calls')
stats.print_stats()
##############################################################################
import profile
if __name__ == '__main__':
profiler = profile.Profile()
profiler.bias = profiler.calibrate(100000)
@@ -0,0 +1,51 @@
import cProfile
import datetime
import functools
def timer(function):
@functools.wraps(function)
def _timer(*args, **kwargs):
start = datetime.datetime.now()
try:
return function(*args, **kwargs)
finally:
end = datetime.datetime.now()
print(f'{function.__name__}: {end - start}')
return _timer
def profiler(function):
@functools.wraps(function)
def _profiler(*args, **kwargs):
profiler = cProfile.Profile()
try:
profiler.enable()
return function(*args, **kwargs)
finally:
profiler.disable()
profiler.print_stats()
return _profiler
@profiler
def profiled_fibonacci(n):
return fibonacci(n)
@timer
def timed_fibonacci(n):
return fibonacci(n)
def fibonacci(n):
if n < 2:
return n
else:
return fibonacci(n - 1) + fibonacci(n - 2)
if __name__ == '__main__':
timed_fibonacci(32)
profiled_fibonacci(32)
@@ -0,0 +1,30 @@
import sys
import pathlib
import pstats
import cProfile
import pyperformance
# pyperformance doesn't expose the benchmarks anymore so we need
# to manually add the path
pyperformance_path = pathlib.Path(pyperformance.__file__).parent
sys.path.append(str(pyperformance_path / 'data-files'))
# Now we can import the benchmark
from benchmarks.bm_float import run_benchmark as bm_float # noqa
def benchmark():
for i in range(10):
bm_float.benchmark(bm_float.POINTS)
if __name__ == '__main__':
profiler = cProfile.Profile()
profiler.runcall(benchmark)
profiler.dump_stats('bm_float.profile')
stats = pstats.Stats('bm_float.profile')
stats.strip_dirs()
stats.sort_stats('calls', 'cumtime')
stats.print_stats(10)
+25
View File
@@ -0,0 +1,25 @@
import itertools
@profile
def primes():
n = 2
primes = set()
while True:
for p in primes:
if n % p == 0:
break
else:
primes.add(n)
yield n
n += 1
if __name__ == '__main__':
total = 0
n = 2000
for prime in itertools.islice(primes(), n):
total += prime
print('The sum of the first %d primes is %d' % (n, total))
@@ -0,0 +1,15 @@
>>> import itertools
# Without itertools.tee:
>>> generator = itertools.count()
>>> list(itertools.islice(generator, 5))
[0, 1, 2, 3, 4]
>>> list(itertools.islice(generator, 5))
[5, 6, 7, 8, 9]
>>> generator_a, generator_b = itertools.tee(itertools.count())
>>> list(itertools.islice(generator_a, 5))
[0, 1, 2, 3, 4]
>>> list(itertools.islice(generator_b, 5))
[0, 1, 2, 3, 4]
@@ -0,0 +1,39 @@
import timeit
import pytest
import functools
class WithSlots:
__slots__ = 'eggs',
class WithoutSlots:
pass
with_slots = WithSlots()
no_slots = WithoutSlots()
@pytest.mark.skip()
def test_set(obj):
obj.eggs = 5
@pytest.mark.skip()
def test_get(obj):
return obj.eggs
timer = functools.partial(
timeit.timeit,
number=20000000,
setup='\n'.join((
f'from {__name__} import with_slots, no_slots',
f'from {__name__} import test_get, test_set',
)),
)
for function in 'test_set', 'test_get':
print(function)
print('with slots', timer(f'{function}(with_slots)'))
print('with slots', timer(f'{function}(no_slots)'))
+23
View File
@@ -0,0 +1,23 @@
import tracemalloc
if __name__ == '__main__':
tracemalloc.start()
# Reserve some memory
x = list(range(1000000))
# Import some modules
import os
import sys
import asyncio
assert os
assert sys
assert asyncio
# Take a snapshot to calculate the memory usage
snapshot = tracemalloc.take_snapshot()
for statistic in snapshot.statistics('lineno')[:10]:
print(statistic)
+21
View File
@@ -0,0 +1,21 @@
try:
import memory_profiler
except ImportError:
print('Please install the memory profiler to run this example')
print('pip install -U memory-profiler')
else:
@memory_profiler.profile
def main():
n = 100000
a = [i for i in range(n)]
b = [i for i in range(n)]
c = list(range(n))
d = list(range(n))
e = dict.fromkeys(a, b)
f = dict.fromkeys(c, d)
assert e
assert f
if __name__ == '__main__':
main()
+36
View File
@@ -0,0 +1,36 @@
import tracemalloc
class SomeClass:
pass
if __name__ == '__main__':
# Initialize some variables to ignore them from the leak
# detection
n = 100000
tracemalloc.start()
# Your application should initialize here
snapshot_a = tracemalloc.take_snapshot()
instances = []
# This code should be the memory leaking part
for i in range(n):
a = SomeClass()
b = SomeClass()
# Circular reference. a references b, b references a
a.b = b
b.a = a
# Force Python to keep the object in memory for now
instances.append(a)
# Clear the list of items again. Now all memory should be
# released, right?
del instances
snapshot_b = tracemalloc.take_snapshot()
statistics = snapshot_b.compare_to(snapshot_a, 'lineno')
for statistic in statistics[:10]:
print(statistic)
@@ -0,0 +1,37 @@
import gc
class SomeClass(object):
def __init__(self, name):
self.name = name
def __repr__(self):
return f'<{self.__class__.__name__}: {self.name}'
# Create the objects
a = SomeClass('a')
b = SomeClass('b')
# Add some circular references
a.b = a
b.a = b
# Remove the objects
del a
del b
# See if the objects are still there
print('Before manual collection:')
for object_ in gc.get_objects():
if isinstance(object_, SomeClass):
print('\t', object_, gc.get_referents(object_))
print('After manual collection:')
gc.collect()
for object_ in gc.get_objects():
if isinstance(object_, SomeClass):
print('\t', object_, gc.get_referents(object_))
print('Thresholds:', gc.get_threshold())
@@ -0,0 +1,12 @@
import gc
import collections
if __name__ == '__main__':
objects = collections.Counter()
for object_ in gc.get_objects():
objects[type(object_)] += 1
print(f'Different object count: {len(objects)}')
for object_, count in objects.most_common(10):
print(f'{count}: {object_}')
+36
View File
@@ -0,0 +1,36 @@
import gc
import weakref
class SomeClass(object):
def __init__(self, name):
self.name = name
def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, self.name)
def print_mem(message):
print(message)
for object_ in gc.get_objects():
if isinstance(object_, SomeClass):
print('\t', object_, gc.get_referents(object_))
# Create the objects
a = SomeClass('a')
b = SomeClass('b')
# Add some weak circular references
a.b = weakref.ref(a)
b.a = weakref.ref(b)
print_mem('Objects in memory before del:')
# Remove the objects
del a
del b
# See if the objects are still there
print_mem('Objects in memory after del:')
@@ -0,0 +1,42 @@
>>> import weakref
>>> weakref.ref(dict(a=123))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: cannot create weak reference to 'dict' object
>>> weakref.ref([1, 2, 3])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: cannot create weak reference to 'list' object
>>> weakref.ref('test')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: cannot create weak reference to 'str' object
>>> weakref.ref(b'test')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: cannot create weak reference to 'bytes' object
>>> a = weakref.WeakValueDictionary(a=123)
Traceback (most recent call last):
...
TypeError: cannot create weak reference to 'int' object
>>> class CustomDict(dict):
... pass
>>> weakref.ref(CustomDict())
<weakref at 0x...; dead>
>>> class SomeClass:
... def __init__(self, name):
... self.name = name
>>> a = SomeClass('a')
>>> b = weakref.proxy(a)
>>> b.name
'a'
>>> del a
>>> b.name
Traceback (most recent call last):
...
ReferenceError: weakly-referenced object no longer exists
+22
View File
@@ -0,0 +1,22 @@
import os
import psutil
def print_usage(message):
process = psutil.Process(os.getpid())
usage = process.memory_info().rss / (1 << 20)
print(f'Memory usage {message}: {usage:.1f} MiB')
def allocate_and_release():
# Allocate large block of memory
large_list = list(range(1000000))
print_usage('after allocation')
del large_list
print_usage('after releasing')
print_usage('initial')
allocate_and_release()
allocate_and_release()
+33
View File
@@ -0,0 +1,33 @@
class Slots(object):
__slots__ = 'index', 'name', 'description'
def __init__(self, index):
self.index = index
self.name = 'slot %d' % index
self.description = 'some slot with index %d' % index
class NoSlots(object):
def __init__(self, index):
self.index = index
self.name = 'slot %d' % index
self.description = 'some slot with index %d' % index
try:
import memory_profiler
except ImportError:
print('Please install the memory profiler to run this example')
print('pip install -U memory-profiler')
else:
@memory_profiler.profile
def main():
slots = [Slots(i) for i in range(25000)]
no_slots = [NoSlots(i) for i in range(25000)]
return slots, no_slots
if __name__ == '__main__':
main()
View File