Skip to content

Commit a4cb73f

Browse files
committed
Add functools into relevant lessons
1 parent 8d67997 commit a4cb73f

2 files changed

Lines changed: 41 additions & 25 deletions

File tree

ultimatepython/advanced/benchmark.py

Lines changed: 35 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,39 +3,49 @@
33
features are added and extra layers of complexity to the main features.
44
Benchmarking is an approach that helps developers use profiling metrics
55
and their code intuition to optimize programs further. This module uses
6-
cProfile to compare the performance of two functions with each other.
6+
cProfile to compare the performance of two functions with each other,
7+
showcasing the impact of caching/memoization with `functools.cache`.
78
"""
89

910
import cProfile
1011
import io
1112
import pstats
12-
import time
13+
from functools import cache
1314

14-
# Module-level constants
15-
_SLEEP_DURATION = 0.001
1615

16+
def fib_naive(n: int) -> int:
17+
"""Calculate Fibonacci number recursively without caching.
1718
18-
def finish_slower() -> None:
19-
"""Finish slower by sleeping more."""
20-
for _ in range(20):
21-
time.sleep(_SLEEP_DURATION)
19+
This has O(2^N) time complexity due to redundant calculations.
20+
"""
21+
if n < 2:
22+
return n
23+
return fib_naive(n - 1) + fib_naive(n - 2)
2224

2325

24-
def finish_faster() -> None:
25-
"""Finish faster by sleeping less."""
26-
for _ in range(10):
27-
time.sleep(_SLEEP_DURATION)
26+
@cache
27+
def fib_cached(n: int) -> int:
28+
"""Calculate Fibonacci number recursively with caching.
29+
30+
This has O(N) time complexity as each value is computed only once.
31+
"""
32+
if n < 2:
33+
return n
34+
return fib_cached(n - 1) + fib_cached(n - 2)
2835

2936

3037
def main() -> None:
3138
# Create a profile instance
3239
profile = cProfile.Profile()
3340

41+
# Clear the cache before benchmarking to ensure clean metrics
42+
fib_cached.cache_clear()
43+
3444
profile.enable()
3545

36-
for _ in range(2):
37-
finish_slower()
38-
finish_faster()
46+
# Compare naive vs cached Fibonacci calculations for N=15
47+
fib_naive(15)
48+
fib_cached(15)
3949

4050
profile.disable()
4151

@@ -46,17 +56,17 @@ def main() -> None:
4656
buffer = io.StringIO()
4757
ps = pstats.Stats(profile, stream=buffer).sort_stats("cumulative")
4858

49-
# Notice how many times each function was called. In this case, the main
50-
# bottleneck for `finish_slower` and `finish_faster` is `time.sleep`
51-
# which occurred 60 times. By reading the code and the statistics, we
52-
# can infer that 40 occurrences came from `finish_slower` and 20 came
53-
# from `finish_faster`. It is clear why the latter function runs faster
54-
# in this case, but identifying insights like this are not simple in
55-
# large projects. Consider profiling in isolation when analyzing complex
56-
# classes and functions
59+
# print_stats writes the tabular profiling info to the buffer
5760
ps.print_stats()
58-
time_sleep_called = any("60" in line and "time.sleep" in line for line in buffer.getvalue().split("\n"))
59-
assert time_sleep_called is True
61+
output = buffer.getvalue()
62+
63+
# Naive recursive fib(15) requires exactly 1,973 calls.
64+
# Cached recursive fib(15) requires exactly 16 calls (inputs 0 to 15).
65+
naive_called = any("1973" in line and "fib_naive" in line for line in output.split("\n"))
66+
cached_called = any("16" in line and "fib_cached" in line for line in output.split("\n"))
67+
68+
assert naive_called is True
69+
assert cached_called is True
6070

6171

6272
if __name__ == "__main__":

ultimatepython/syntax/function.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"""
1010

1111
from collections.abc import Callable
12+
from functools import partial
1213
from typing import Any
1314

1415

@@ -55,6 +56,11 @@ def main() -> None:
5556
add_result_int = add(1, 2)
5657
assert add_result_int == 3
5758

59+
# We can use `functools.partial` to create a new function with some
60+
# arguments pre-filled, which is a cleaner alternative to lambdas in many cases.
61+
add_five = partial(add, 5)
62+
assert add_five(10) == 15
63+
5864
# The `add` function can be used for strings as well
5965
add_result_string = add("hello", " world")
6066
assert add_result_string == "hello world"

0 commit comments

Comments
 (0)