====== cs20ps23as10: Profiling via Decoration ====== ===== Goals ===== * Practice with decorators in Python. * Experiment with implementing function-call profiling via decorators. ----- ====== Prerequisites ====== This assignment requires familiarity with the [[:lecture materials/]] presented in class through [[:lecture materials/week 10]]. ----- ====== Assignment ====== You shall define a function named ''profile'' in a module named ''call_profiler''. Calls to functions decorated with ''call_profiler.profile'' will be profiled in a manner similar to that of ''cProfile'', i.e. the total number of calls to decorated functions during an interpreter session will be recorded, as will the cumulative amount of time spent executing each function. Profiling information shall be supplied via four other functions in the ''call_profile'' module, as demonstrated below. ===== Starter Code ===== """ Provides decorator function "profile" that counts calls and cumulative execution time for all decorated functions for the duration of an interpreter session. """ __author__ = 'A student in CS 20P, someone@jeff.cis.cabrillo.edu' import time def profile(function): """ Decorates a function so that the number of calls and cumulative execution time of all calls can be reported by call_count() and cumulative_time(), respectively. Execution time is measured by calling time.perf_counter() before and after a call to the decorated function. """ pass # TODO def call_count(function): """ Returns the number of times a given function has been called during this interpreter session, assuming the function has been decorated by profile(). """ pass # TODO def call_counts(): """ Returns a dictionary mapping functions decorated by profile() to the number of times they have been called during this interpreter session. """ pass # TODO def cumulative_time(function): """ Returns the cumulative amount of time (in seconds) that have been spent executing calls to a given function during this interpreter session, assuming the function has been decorated by profile(). """ pass # TODO def cumulative_times(): """ Returns a dictionary mapping functions decorated by profile() to the cumulative amount of time (in seconds) that have been spent executing calls to a given function during this interpreter session. """ pass # TODO ===== Demo ===== Here is a module with a few functions from previous lectures, now decorated with ''call_profiler.profile'': """ Various functions for experimenting with the call_profiler module. """ __author__ = 'Jeffrey Bergamini for CS 20P, jeffrey.bergamini@cabrillo.edu' import call_profiler @call_profiler.profile def dna(sequence) -> dict[str, int]: """ Counts the bases in a DNA string. :param sequence: A DNA string (expected to contain A/C/G/T characters). :return: A dict[str, int] with keys in bases 'ACGT', and associated base counts. """ return {base: sequence.count(base) for base in 'ACGT'} @call_profiler.profile def euler_004(): """ Single statement with walrus. """ return max(a * b for a in range(100, 1000) for b in range(100, 1000) if (prod_str := str(a * b)) == prod_str[::-1]) @call_profiler.profile def euler_009(): """ Generator expression passed to next()—we know there can be only one solution. """ return next(a * b * c for a in range(1, 334) for b in range(a + 1, 1000 - a) if a**2 + b**2 == (c := 1000 - a - b)**2) And here is a REPL session demonstrating the profiling behavior: >>> import call_profiler_demo >>> import call_profiler >>> call_profiler.call_counts() {: 0, : 0, : 0} >>> call_profiler.call_count(call_profiler_demo.euler_004) 0 >>> call_profiler.cumulative_times() {: 0.0, : 0.0, : 0.0} >>> call_profiler.cumulative_time(call_profiler_demo.euler_004) 0.0 >>> call_profiler_demo.euler_004() 906609 >>> call_profiler_demo.euler_004() 906609 >>> call_profiler_demo.euler_009() 31875000 >>> call_profiler_demo.dna('GATTACA') {'A': 3, 'C': 1, 'G': 1, 'T': 2} >>> call_profiler_demo.dna(open('/srv/datasets/chromosome4').read()) {'A': 3030352, 'C': 2102095, 'G': 2092163, 'T': 3009609} >>> call_profiler.call_counts() {: 2, : 2, : 1} >>> call_profiler.call_count(call_profiler_demo.euler_004) 2 >>> call_profiler.cumulative_times() {: 0.09427983299246989, : 0.20388466698932461, : 0.02965154201956466} >>> call_profiler.cumulative_time(call_profiler_demo.euler_004) 0.20388466698932461 ===== Lines of Code! ===== Just for fun, as submissions are received, this table will be updated and ranked with regard to the number of lines in a fully functional solution, as measured by [[https://dwheeler.com/sloccount/|SLOCCount]]. Fewer lines isn't always better, but it's interesting to see by how much different approaches vary.
RankSLOC (lines)User
----- ====== Submission ====== Submit ''call_profiler.py'' via [[info:turnin]]. {{https://jeff.cis.cabrillo.edu/images/feedback-robot.png?nolink }} //**Feedback Robot**// This project has a feedback robot that will run some tests on your submission and provide you with a feedback report via email within roughly one minute. Please read the feedback carefully. ====== Due Date and Point Value ====== Due at 23:59:59 on the date listed on the [[:syllabus|syllabus]]. ''Assignment 10'' is worth 60 points. Possible point values per category: --------------------------------------- call_counts() 15 call_count() 15 cumulative_times() 15 cumulative_time() 15 Possible deductions: Style and practices 10–20% Possible extra credit: Submission via Git 5% ---------------------------------------