a Summer School by the G-Node, the Bernstein Center for Computational
Neuroscience Munich and the Graduate School of Systemic Neurosciences
Use this to post questions, links, comments, and sometimes solutions to exercises.
Write a decorator which prints the arguments and the return value of the wrapped function.
@logger def g(x): return 2 * x @logger def f(x, y): return g(x) + g(y)
>>> f(5, 6) f is called with args [5, 6] kwargs {} g is called with args [5] kwargs {} g returns 10 g is called with args [6] kwargs {} g returns 12 f returns 22
Write 'timeit' decorator which prints how long a function took to execute.
import time @timeit def loooong(): time.sleep(5) return 'ans'
>>> looong() looong took 5.0323423s ans
Write a decorator which caches the results of some function. Store the results of every invocation. Every time the function is called with the same arguments, simply retrieve the value from storage. Otherwise call the function.
>>> @cached >>> def f(x): print('here') >>> f(3) here >>> f(3) >>>
Test with:
def factorial(n): if n <= 1: return 1 else: return n * factorial(n-1)
Write a decorator which prints a warning the first
time a given function is executed. This is a modification
of deprecate()
from previous exercise.
@deprecate('do not use') def f(): pass
>>> f() f is deprecated, do not use >>> f() >>> f()
The trick is how to store the state!
When a function returns a list of results, we might need to gather those results in a list:
def lucky_numbers(n): ans = [] for i in range(n): if i % 7 != 0: continue if sum(int(digit) for digit in str(i)) % 3 != 0: continue ans.append(i) return ans
This looks much nicer when written as a generator. First convert lucky_numbers to be a generator.
Later, write listize
decorator which gathers the results from a generator
and returns a list and use it to wrap the new lucky_numbers().
Alternatively, write arrayize
decorator which return the results
in a numpy array.
Before we wrote a decorator which would print how long a function took to execute. Now write a context manager which does the same thing.
>>> with logtime_cm(): ... time.sleep(3) Execution took 3.00001s
This is synthesized from a real program that I use to analyze results and create graphs. Matplotlib figures can be plotted on screen, and they can also be saved to file with figure.savefig()
.
Write a context manager which gives you a matplotlib figure object,
and either saves the plot to a file or pops it up on screen,
depending on a global parameter SAVEFIGS
(in a real program
this parameter would be settable by a commandline option).
with save_or_plot('name') as f: ax = f.gca() ax.plot([0, 3, 2, 5]) ax.set_xlabel('x') ax.set_ylabel('y')
This example comes from unit testing. We want to make sure that we raise the right exceptions on errors.
Write a cm 'assert_raises' that
>>> with assert_raises(ZeroDivisionError): ... 1 / 0 >>> with assert_raises(ZeroDivisionError): ... 0[0] / 0 Traceback (most recent call last): ... AssertionError: expected ZeroDivisionError not AttributeError >>> with assert_raises(ZeroDivisionError): ... 0 / 1 Traceback (most recent call last): ... AssertionError: expected ZeroDivisionError exception
The OS call alarm can be used to interrupt a process:
import signal import time def _handler(signum, frame): print('_handler called for signal', signum) oldhandler = signal.signal(signal.SIGALRM, _handler) signal.alarm(3) time.sleep(100)
Write a context manager which limits the execution time to the given number of seconds:
>>> with timelimit(5): ... looong_computation() RuntimeError Traceback (most recent call last) ... RuntimeError: over the deadline
Notes: signal.signal returns the previous handler that was installed. It should be restored after our context manager is done.
Some docs on the web: