%matplotlib inline
#%config PromptManager.in_template = '>>> '
#%config PromptManager.out_template = ' '
from pygments import highlight
from pygments.lexers import PythonLexer
from pygments.formatters import HtmlFormatter
import IPython
class _merge_html:
def __init__(self, *args):
self._html = ''.join(x._repr_html_() for x in args)
def _repr_html_(self):
return self._html
def showfile(filename, *extras):
with open(filename) as f:
code = f.read()
formatter = HtmlFormatter()
html = IPython.display.HTML(
'<style type="text/css">{}</style>{}'.format(
formatter.get_style_defs('.highlight'),
highlight(code, PythonLexer(), formatter)))
link = IPython.display.FileLink(filename)
extra = (IPython.display.FileLink(arg) for arg in extras)
return _merge_html(link, html, *extra)
Zbigniew Jędrzejewski-Szmek
George Mason University
def countdown(n):
while n > 0:
yield n
n -= 1
counter = countdown(3)
next(counter)
3
next(counter)
2
next(counter)
1
next(counter)
--------------------------------------------------------------------------- StopIteration Traceback (most recent call last) <ipython-input-8-ada38ea300f6> in <module>() ----> 1 next(counter) StopIteration:
import networkx as nx
G=nx.DiGraph()
G.add_edge("generator function", "generator", label='call')
G.add_edge("generator", "item", label='next()')
pos = {"generator function":(0,3), "generator":(1,2), "item":(2,1)}
nx.draw(G, with_labels=True, pos=pos)
edges = nx.get_edge_attributes(G, 'label')
_ = nx.draw_networkx_edge_labels(G, pos=pos, edge_labels=edges)
yield
def not_a_generator():
return
def a_generator():
yield
return
def also_a_generator():
if False:
yield
return
Also: execute it!
showfile('files/example1.py', 'files/test_example1.py')
# Write a generator which takes a list (or any sequence)
# and returns items from the list in random order.
def randomized(seq):
"""Iterate over seq returning items in random order.
Making a copy of the argument or mutating the argument is *not
allowed*.
>>> list(randomized('abcdefghi'))
['b', 'a', 'e', 'd', 'f', 'c', 'h', 'i', 'g']
"""
...
!py.test-3.4 files/test_example1.py
============================= test session starts ============================== platform linux -- Python 3.4.1 -- py-1.4.23 -- pytest-2.6.0 plugins: cov, instafail collected 2 items files/test_example1.py F. =================================== FAILURES =================================== ______________________________ test_completeness _______________________________ def test_completeness(): > assert set(randomized('abcdef')) == set('abcdef') E TypeError: 'NoneType' object is not iterable files/test_example1.py:4: TypeError ====================== 1 failed, 1 passed in 0.04 seconds ======================
def generator_function():
print('--start--')
yield 1
print('--middle--')
yield 2
print('--stop--')
generator = generator_function()
next(generator)
--start--
1
next(generator)
--middle--
2
next(generator)
--stop--
--------------------------------------------------------------------------- StopIteration Traceback (most recent call last) <ipython-input-38-1d0a8ea12077> in <module>() ----> 1 next(generator) StopIteration:
next(g)
calls g.next()
(Python 2) or g.__next__()
(Python 3, 4,...)g.send()
enables bidirectional communicationdef bidirectional_generator_function():
print('--start--')
val = yield 1
print('--got', val)
print('--middle--')
val = yield 2
print('--got', val)
print('--stop--')
generator = bidirectional_generator_function()
next(generator)
--start--
1
generator.send('value')
--got value --middle--
2
next(generator)
--got None --stop--
--------------------------------------------------------------------------- StopIteration Traceback (most recent call last) <ipython-input-80-1d0a8ea12077> in <module>() ----> 1 next(generator) StopIteration:
generator = bidirectional_generator_function()
generator.send('something')
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-81-6de2b08dcc74> in <module>() 1 generator = bidirectional_generator_function() ----> 2 generator.send('something') TypeError: can't send non-None value to a just-started generator
generator = bidirectional_generator_function()
generator.send(None)
--start--
1
showfile('files/example2.py', 'files/test_example2.py')
# Write a range replacement (adjrange, short for "adjustable range"),
# which can be prodded with .send() to change the step.
#
# This could be useful e.g. in a numerical integration routine, where
# we want to increase the step size in boring areas, and decrease in
# areas of high variability.
def adjrange(start, stop, step):
"""A range()/xrange() replacement with adjustable step.
>>> x = adjrange(0, 7, 1)
>>> next(g)
0
>>> next(g)
1
>>> g.send(2)
>>> next(g)
3
>>> next(g)
5
>>> next(g) # doctest: +ELLIPSIS
Traceback:
...
StopIteration: ...
"""
# fixme
# Bonus: fix the function so that adjrange(stop) works
skip?
import math
def f(n):
print('will call math.sqrt')
ans = math.sqrt(n)
print('math.sqrt has returned')
return ans
def g(n):
print('will call f')
ans = 2 * f(n)
print('f has returned')
return ans
g(-1)
will call f will call math.sqrt
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-120-5bdd1269a957> in <module>() 13 return ans 14 ---> 15 g(-1) <ipython-input-120-5bdd1269a957> in g(n) 9 def g(n): 10 print('will call f') ---> 11 ans = 2 * f(n) 12 print('f has returned') 13 return ans <ipython-input-120-5bdd1269a957> in f(n) 3 def f(n): 4 print('will call math.sqrt') ----> 5 ans = math.sqrt(n) 6 print('math.sqrt has returned') 7 return ans ValueError: math domain error
def inverse(val):
return 1/val
def g(val):
try:
return inverse(val)
except Exception as e:
print('caught exception', e.__class__.__name__, ':', e)
return 0
print(g(5))
print(g(0))
0.2 caught exception ZeroDivisionError : division by zero 0
def f(flag):
try:
if flag:
no_such_func()
return 11
except Exception as e:
print('caught exception', e.__class__.__name__, ':', e)
finally:
print('finally called')
f(False)
finally called
11
f(True)
caught exception NameError : name 'no_such_func' is not defined finally called
def sqrt(n):
if n < 0:
raise ValueError('no negativity please')
return n ** 0.5
print(sqrt(5))
print(sqrt(-3))
2.23606797749979
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) <ipython-input-152-02012ba21434> in <module>() 1 print(sqrt(5)) ----> 2 print(sqrt(-3)) <ipython-input-151-952e42f28e1c> in sqrt(n) 1 def sqrt(n): 2 if n < 0: ----> 3 raise ValueError('no negativity please') 4 return n ** 0.5 ValueError: no negativity please
random.random()
to decide which exception to throw (e.g. ZeroDivisionError
, ValueError
, Exception
, TypeError
, ...).try
..except
handler which catches multiple exception types using multiple except
clauses and prints out the exceptions.import math
def sqrt(n):
try:
print('will call math.sqrt')
ans = math.sqrt(n)
print('math sqrt has returned')
return ans
except ValueError:
print('caught exception')
return math.sqrt(-n) * 1j
print('+1:', sqrt(+1))
print('-1:', sqrt(-1))
will call math.sqrt math sqrt has returned +1: 1.0 will call math.sqrt caught exception -1: 1j
def generator_function(): # same as before
print('--start--')
yield 1
print('--middle--')
yield 2
print('--stop--')
generator = generator_function()
next(generator)
--start--
1
generator.throw(ZeroDivisionError)
--------------------------------------------------------------------------- ZeroDivisionError Traceback (most recent call last) <ipython-input-85-5de9a8cb7d61> in <module>() ----> 1 generator.throw(ZeroDivisionError) <ipython-input-84-c4e096b0e8ba> in generator_function() 1 def generator_function(): # same as before 2 print('--start--') ----> 3 yield 1 4 print('--middle--') 5 yield 2 ZeroDivisionError:
.close()
is used to destroy resources tied up in the generatordef generator_function():
try:
yield 'hello'
except GeneratorExit:
print('bye!')
g = generator_function()
next(g)
'hello'
g.close()
bye!
showfile('files/example3.py')
# 'break' works on the innermost loop. If we want to break out
# of the outer loop, we often have to resort to flag variable.
# Rewrite the following example to use .close() to stop the
# outer loop.
from __future__ import print_function
def counter(n):
i = 1
while i <= n:
yield i
i += 1
def print_table():
outer = counter(10)
finished = False # <---- get rid of this
total, limit = 0, 100
for i in outer:
inner = counter(i)
print(i, end=': ')
for j in inner:
print(i * j, end=' ')
total += i * j
if total >= limit:
finished = True # <----- and this
break
print()
if finished: # <----- and also this
break #
print('total:', total)
if __name__ == '__main__':
print_table()
g = countdown(5)
g.gi_frame.f_locals
g.__sizeof__()
def gg(g):
yield from g
ggg = gg(g)
list(ggg)
[5, 4, 3, 2, 1]
Summary This amazing feature appeared in the language almost apologetically and with concern that it might not be that useful.Bruce Eckel
def deco(f):
return f
@deco
def function():
print('in function')
def function(): # old syntax before the introduction of @
print('in function')
function = deco(function)
class A(object):
def method(self, arg):
return arg
a = A()
print(a.method(1))
1
class A(object):
@classmethod
def cmethod(cls, arg):
return arg
a = A()
print(a.cmethod(2))
print(A.cmethod(3))
2 3
class A(object):
@staticmethod
def method(arg):
return arg
a = A()
print(a.method(4))
print(a.method(5))
4 5
class A(object):
@property
def not_a_method(self):
return 'value'
a = A()
print(a.not_a_method)
value
@property
makes attributes read-onlyclass Square(object):
def __init__(self, edge):
self.edge = edge
@property
def area(self):
"""Computed area."""
return self.edge ** 2
s = Square(2)
s.area
4
s.area = 3
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-99-01915eb21e8a> in <module>() ----> 1 s.area = 3 AttributeError: can't set attribute
@property
-ies can be also be read-writeclass Square(object):
def __init__(self, edge):
self.edge = edge
@property
def area(self):
"Compute area from edge"
return self.edge ** 2
@area.setter
def area(self, area):
"Set edge by computing from area"
self.edge = area ** 0.5
s = Square(2)
s.area
4
s.area = 9
s.edge
3.0
property
triple: setter, getter, deleters.edge
calls area.getx
@property
s.edge
calls area.setx
@area.setter
s.edge
calls area.delx
@area.deleter
A decorator can be used to:
do stuff when a function is created
or replace a function (with a wrapper)
(this means that the decorator returns the argument it received)
FUNCTIONS_DEFINED_BY_ORDER = []
def remember_this_function(f):
print('just decorating', f)
FUNCTIONS_DEFINED_BY_ORDER.append(f)
return f
@remember_this_function
def my_func():
print('just doing my job')
just decorating <function my_func at 0x7ff48d17fd90>
my_func()
FUNCTIONS_DEFINED_BY_ORDER
just doing my job
[<function __main__.my_func>]
showfile('files/decorator_attribute.py')
# Sometimes we want to mark a function with an attribute.
# Define the decorator testthis which will set the
# attribute ._run_test on the function object. That
# function will then be tested. Run tests with:
# python3 decorator_attribute.py
# or
# python2 decorator_attribute.py
from __future__ import print_function
def testthis(func):
...
# This should be run as a test
def example_1():
print('testing this')
# This should be run as a test
def example_2():
print('testing that')
if __name__ == '__main__':
print('running the tests')
for name, value in dict(locals()).items():
if getattr(value, '_run_test', False):
print('running', name)
value()
# To test run:
# python3 decorator_attribute.py
# or
# python2 decorator_attribute.py
# and check if the output contains
# running example_1
# running example_2
# No automatic test, sorry!
# Something like this done by
# @unittest.skip, @unittest.expectedFailure,
# @pytest.mark.skipif, @pytest.mark.xfail.
Example: log the arguments and return value whenever a function is called
def logargs(f):
def wrapper(*args, **kwargs):
print('-- calling', f.__name__, 'with', args, 'and', kwargs)
ans = f(*args, **kwargs)
print('-- calling', f.__name__, 'results in', ans)
return ans
return wrapper
@logargs
def thinker(x, y, z):
"This function ponders for a moment and then returns the result"
print('let me think')
return x + ' ' + y + ' ' + z
thinker('mala', 'kava', z='s mlijekom')
-- calling thinker with ('mala', 'kava') and {'z': 's mlijekom'} let me think -- calling thinker results in mala kava s mlijekom
'mala kava s mlijekom'
showfile('files/deprecation.py')
# The decorator below is supposed to log a warning,
# but just once, not to annoy the user too much.
import math
def deprecate(f):
"""Log a warning when the function is called for the first time.
>>> @deprecate
... def f():
... pass
>>> f()
Function f is deprecated, please use something else!
>>> f()
>>> f()
"""
...
return f
@deprecate
def square_root(x):
return math.sqrt(x)
# To test this file, use:
# py.test-3.4 --doctest-modules deprecation.py
# or
# python3 -m doctest deprecation.py
print(thinker)
help(thinker)
<function logargs.<locals>.wrapper at 0x7ff48d16d620> Help on function wrapper in module __main__: wrapper(*args, **kwargs)
%%python3
def logargs(f):
def wrapper(*args, **kwargs):
print('-- calling', f.__name__, 'with', args, 'and', kwargs)
ans = f(*args, **kwargs)
print('-- calling', f.__name__, 'result is', ans)
return ans
return wrapper
@logargs
def f():
1/0
f()
-- calling f with () and {}
Traceback (most recent call last): File "<stdin>", line 14, in <module> File "<stdin>", line 5, in wrapper File "<stdin>", line 12, in f ZeroDivisionError: division by zero
__doc__
__module__
and __name__
__dict__
eval
is required for the rest :(decorator
compiles functions dynamicallyimport functools
def logargs(f):
def wrapper(*args, **kwargs):
print('-- calling', f.__name__, 'with', args, 'and', kwargs)
ans = f(*args, **kwargs)
print('-- calling', f.__name__, 'result is', ans)
return ans
return functools.update_wrapper(wrapper, f)
# This ^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^ is new
@logargs
def f():
"This function doesn't do anything really."
print('here')
f()
-- calling f with () and {} here -- calling f result is None
print(f)
help(f)
<function f at 0x7ff49547a048> Help on function f in module __main__: f() This function doesn't do anything really.
n
Modify decorator from previous exercise to use functools.update_wrapper
or functools.wraps
.
import functools
def deprecated(message):
def decorator(func):
seen = False
def wrapper(*args, **kwargs):
nonlocal seen
if not seen:
print('{} is depracted: {}'.format(f.__name__, message))
seen = True
return func(*args, **kwargs)
return functools.update_wrapper(wrapper, func)
return decorator
@deprecated('use math.sqrt instead')
def mysqrt(n):
"""Compute the square root using Heron's method.
Taken from http://stackoverflow.com/posts/12851282/revisions
"""
if n == 0:
return 0
if n < 1:
return mysqrt(n * 4) / 2
if 4 <= n:
return mysqrt(n / 4) * 2
x = (n + 1) / 2
for i in range(5):
x = (x + n/x) / 2
x = (x + n/x) / 2
return x
print('5:', mysqrt(5))
print('3:', mysqrt(3))
f is depracted: use math.sqrt instead 5: 2.23606797749979 3: 1.7320508075688772
import functools
import math
def deprecated(message):
"Create a decorator which will emit message."
print('creating decorator')
def decorator(func):
"Decorate a function to emit the message on first call."
print('decorating function', func.__name__)
func.warning_seen = False
def wrapper(*args, **kwargs):
print('the wrapper for', func.__name__, 'is running')
if not func.warning_seen:
print('{} is depracted: {}'.format(f.__name__, message))
func.warning_seen = True
return func(*args, **kwargs)
return functools.update_wrapper(wrapper, func)
return decorator
@deprecated('use math.sqrt instead')
def mysqrt(n):
print('mysqrt is running')
return math.sqrt(n)
creating decorator decorating function mysqrt
print('5:', mysqrt(5))
print('3:', mysqrt(3))
the wrapper for mysqrt is running f is depracted: use math.sqrt instead mysqrt is running 5: 2.23606797749979 the wrapper for mysqrt is running mysqrt is running 3: 1.7320508075688772
def deco(f):
print(f.__name__, 'just got defined')
return f
@deco
class A(object):
def __init__(self):
print(self.__class__.__name__, 'object is created')
print('A:', A)
A just got defined A: <class '__main__.A'>
a = A()
print('a:', a)
A object is created a: <__main__.A object at 0x7ff48d1c55c0>
See Flask for a beautiful application of this mechanism.
Decorators — summary:
functools
help preserve __doc__
, __name__
, __module__
import os
f = open('temporary_file', 'w')
try:
f.write('blah blah')
# do some processing using the temporary file
finally:
os.unlink(f.name)
General pattern
try
to use the resourcesfinally
cleanupclass Manager(object):
def __enter__(self): return None
def __exit__(self, *args): pass
def do_something(arg): pass
manager = Manager()
with manager as var:
do_something(var)
manager = Manager()
var = manager.__enter__()
try:
do_something(var)
finally:
manager.__exit__(...)
import contextlib
@contextlib.contextmanager
def some_generator(*args):
print('doing setup')
try:
yield 'value-of-variable'
finally:
print('doing cleanup')
pass
import os
@contextlib.contextmanager
def temporary_file(name):
f = open(name, 'w')
try:
yield f
finally:
os.unlink(f.name)
import time
time.time()
1410261874.6944594
showfile('files/cm_timer.py')
# We want to measure (and log) how long a chunk of code
# took to execute.
#
# We can use time.time() function to get a timestamp right
# before and right after, and print out the difference.
#
# Implement logging the duration of the execution
from __future__ import print_function
import time
import random
def timeit():
"""Context manager which logs the duration of execution.
>>> with timeit(): # doctest: +ELLIPSIS
... time.sleep(1)
Calculations took 1...s
>>> with timeit(): # doctest: +ELLIPSIS
... time.sleep(3)
Calculations took 3...s
"""
...
print('Calculations took XXXs')
def long_haul(*args):
"Uses time.sleep() to simulate long calculations"
time.sleep(5 * random.random())
return sum(args)
if __name__ == '__main__':
print('Will do calculations now')
with timeit():
ans = long_haul(1, 2, 3)
print(ans)
# Please note:
# We could do it also in the traditional way
# using something like:
#
# print('Will do calculations now')
# start = time.time()
# ans = long_haul()
# duration = time.time() - start
# print('Calculations took {}s'.format(duration))
#
# but this is harder to read, and leaves the
# 'start' and 'duration' names in the local scope.
#
# To test this file, use:
# py.test-3.4 --doctest-modules cm_timer.py -v
# or
# python3 -m doctest cm_timer.py
try
..finally
callsdef buggy_code():
raise Exception('boo boo')
def buggy_cleanup():
print('buggy_cleanup running')
raise Exception('bug in cleanup')
try:
try:
print('working')
buggy_code()
print('work done')
finally:
print('finalizer a')
buggy_cleanup()
print('cleanup done')
finally:
print('finalizer b')
working finalizer a buggy_cleanup running finalizer b
--------------------------------------------------------------------------- Exception Traceback (most recent call last) <ipython-input-136-40bfd9cbedb1> in <module>() 6 finally: 7 print('finalizer a') ----> 8 buggy_cleanup() 9 print('cleanup done') 10 finally: <ipython-input-135-39547461b499> in buggy_cleanup() 4 def buggy_cleanup(): 5 print('buggy_cleanup running') ----> 6 raise Exception('bug in cleanup') Exception: bug in cleanup
@contextlib.contextmanager
def cm(name):
print('--entering', name)
try:
yield
finally:
print('--exiting', name)
with cm('outer'), cm('inner'):
print('real stuff')
--entering outer --entering inner real stuff --exiting inner --exiting outer
Context manager must have __enter__()
and __exit__()
.
__enter__()
and __exit__()
methods__enter__()
returns self
f = open('some-file')
help(f.__enter__)
help(f.__exit__)
Help on built-in function __enter__: __enter__(...) method of _io.TextIOWrapper instance Help on built-in function __exit__: __exit__(...) method of _io.TextIOWrapper instance
with open('/etc/fstab', 'rt') as f:
with open('/tmp/g', 'wt') as g:
g.write(f.read())
f.closed, g.closed
(True, True)
with open('/etc/fstab', 'rt') as f, \
open('/tmp/g', 'wt') as g:
g.write(f.read())
showfile('files/cm_assert_raises.py')
# This happens most often in testing. We would like to make sure that
# a block of code raises an exception of the certain type.
# If it does not raise an exception, *we* raise an error.
# If it raises an unexpected exception, we raise an error *too*.
# Write a context manager which does checks that an exception is
# raised properly and convert the example below. Make things
# less ugly too.
import math
def log_of_sqrt(n):
return math.log(math.sqrt(n))
def assert_raises(exception_type):
...
def test_sqrt():
try:
log_of_sqrt(-5)
except Exception as e:
if isinstance(e, ValueError): # what is Pythonic way
# to check exception type?
pass
else:
raise AssertionError('bad exception')
finally:
raise AssertionError('no expected exception')
# Note: this is implemented by
# pytest.raises,
# unittest.TestCase.assertRaises.
try..except..finally..else
cleanupIPython.display.Image('files/tux-asleep-2.png')
showfile('files/capture_stdout.py')
# Write a context manager which temporarily replaces
# sys.stdout with a io.StringIO() object and thus
# captures the output of print().
import contextlib
@contextlib.contextmanager
def capture_stdout():
r"""Replace sys.stdout with String() for the block.
>>> with capture_stdout() as out:
... print('goo')
>>> out.getvalue()
'goo\n'
"""
try:
yield ...
finally:
pass
# To test this file, use:
# py.test-3.4 --doctest-modules capture_stdout.py
# or
# python3 -m doctest capture_stdout.py
showfile('files/test_example3.py')
import contextlib
import io
from example3 import print_table
from capture_stdout import capture_stdout
def test_sum():
with capture_stdout() as output:
print_table()
assert 'sum: 115' in output.getvalue()
showfile('files/memoize.py', 'files/test_memoize.py', 'files/test_memoize_mutable.py')
# Implement a cache, which will store the values
# returned by the function for specific inputs.
import functools
def memoize(func):
...
return func
@memoize
def fibonacci(n):
"""Returns fibonnaci number n.
See http://en.wikipedia.org/wiki/Fibonacci_number.
>>> print(fibonacci.cache)
{}
>>> fibonacci(1)
1
>>> fibonacci(2)
1
>>> fibonacci(10)
55
>>> fibonacci.cache[10]
55
>>> fibonacci(40)
102334155
"""
assert n >= 0
if n < 2:
return n
else:
return fibonacci(n-1) + fibonacci(n-2)
# Test this with
# py.test-3.4 -v test_memoize.py
# Once that passes, try
# py.test-3.4 -v test_memoize_mutable.py