Skip to content

Instantly share code, notes, and snippets.

@sahands
Last active December 19, 2015 23:19
Show Gist options
  • Save sahands/6034061 to your computer and use it in GitHub Desktop.
Save sahands/6034061 to your computer and use it in GitHub Desktop.
A Study of Python's More Advanced Features: Part II - Closures, Decorators and functools
import time
from functools import wraps
def cached(timeout, logged=False):
"""Decorator to cache the result of a function call.
Cache expires after timeout seconds.
"""
def decorator(func):
if logged:
print "-- Initializing cache for", func.__name__
cache = {}
@wraps(func)
def decorated_function(*args, **kwargs):
if logged:
print "-- Called function", func.__name__
key = repr(args) + repr(kwargs)
result = None
if key in cache:
if logged:
print "-- Cache hit for", func.__name__, key
(cache_hit, expiry) = cache[key]
if time.time() - expiry < timeout:
result = cache_hit
elif logged:
print "-- Cache expired for", func.__name__, key
elif logged:
print "-- Cache miss for", func.__name__, key
# No cache hit, or expired
if result == None:
result = func(*args, **kwargs)
cache[key] = (result, time.time())
return result
return decorated_function
return decorator
@cached(10, True)
def fib(n):
"""Returns the n'th Fibonacci number."""
if n == 0 or n == 1:
return 1
return fib(n - 1) + fib(n - 2)
print "%s - %s" % (fib.__name__, fib.__doc__)
print "Dumping function's closure:"
print "Testing - F(4) = %d" % fib(4)
for c in fib.func_closure:
print c.cell_contents
print
@cached(2, True)
def cached_time():
return time.time()
for i in xrange(5):
print cached_time()
time.sleep(1)
# Output:
-- Initializing cache for fib
fib - Returns the n'th Fibonacci number.
Dumping function's closure:
-- Called function fib
-- Cache miss for fib (4,){}
-- Called function fib
-- Cache miss for fib (3,){}
-- Called function fib
-- Cache miss for fib (2,){}
-- Called function fib
-- Cache miss for fib (1,){}
-- Called function fib
-- Cache miss for fib (0,){}
-- Called function fib
-- Cache hit for fib (1,){}
-- Called function fib
-- Cache hit for fib (2,){}
Testing - F(4) = 5
{'(3,){}': (3, 1374300190.333688), '(2,){}': (2, 1374300190.33365), '(4,){}': (5, 1374300190.333725), '(1,){}': (1, 1374300190.333604), '(0,){}': (1, 1374300190.333648)}
<function fib at 0x109b331b8>
True
10
-- Initializing cache for cached_time
-- Called function cached_time
-- Cache miss for cached_time (){}
1374300190.33
-- Called function cached_time
-- Cache hit for cached_time (){}
1374300190.33
-- Called function cached_time
-- Cache hit for cached_time (){}
-- Cache expired for cached_time (){}
1374300192.33
-- Called function cached_time
-- Cache hit for cached_time (){}
1374300192.33
-- Called function cached_time
-- Cache hit for cached_time (){}
-- Cache expired for cached_time (){}
1374300194.34
def dump_closure(f):
if hasattr(f, "func_closure") and f.func_closure is not None:
print "- Dumping function closure for %s:" % f.__name__
for i, c in enumerate(f.func_closure):
print "-- cell %d = %s" % (i, c.cell_contents)
else:
print " - %s has no closure!" % f.__name__
def return_add_stuff_function(x, z):
some_constant = [0]
def add_stuff(y):
return some_constant + x + y + z
return add_stuff
z = [3]
f = return_add_stuff_function([1], z)
print f([2])
dump_closure(f)
print
z.append(4)
print f([2])
dump_closure(f)
# Output:
[0, 1, 2, 3]
- Dumping function closure for add_stuff:
-- cell 0 = [1]
-- cell 1 = [0]
-- cell 2 = [3]
[0, 1, 2, 3, 4]
- Dumping function closure for add_stuff:
-- cell 0 = [1]
-- cell 1 = [0]
-- cell 2 = [3, 4]
def make_dual(relation):
@wraps(relation, ['__name__', '__doc__'])
def dual(x, y):
return relation(y, x)
return dual
def dual_ordering(cls):
"""Class decorator that reverses all the orderings"""
for func in ['__lt__', '__gt__', '__ge__', '__le__']:
if hasattr(cls, func):
setattr(cls, func, make_dual(getattr(cls, func)))
return cls
@dual_ordering
class rs(str):
pass
x = rs("1")
y = rs("2")
print x < y
print x <= y
print x > y
print x >= y
# Output:
False
False
True
True
def ignore(return_value=None):
"""Decorator that makes the functional call be ignored returning return_value immediately."""
def wrapper(func):
def decorated_function(*args, **kwargs):
return return_value
return decorated_function
return wrapper
@ignore(5)
def add(x, y):
return x + y
@ignore()
def print_something():
print "Something!"
print add(1, 2)
print_something()
# Output, notice that "Something" is NOT printed:
5
import time
from functools import wraps
def logged(time_format, name_prefix=""):
def decorator(func):
if hasattr(func, '_logged_decorator') and func._logged_decorator:
return func
@wraps(func)
def decorated_func(*args, **kwargs):
print "Log: '%s' called on %s with arguments: %s, %s" % (
name_prefix + func.__name__,
time.strftime(time_format),
args,
kwargs
)
return func(*args, **kwargs)
decorated_func._logged_decorator = True
return decorated_func
return decorator
def log_method_calls(time_format):
def decorator(cls):
for o in dir(cls):
if o.startswith('__'):
continue
a = getattr(cls, o)
if hasattr(a, '__call__'):
decorated_a = logged(time_format, cls.__name__ + ".")(a)
setattr(cls, o, decorated_a)
return cls
return decorator
@log_method_calls("%b %d %Y - %H:%M:%S")
class A(object):
def test1(self):
print "test1"
@log_method_calls("%b %d %Y - %H:%M:%S")
class B(A):
def test1(self):
super(B, self).test1()
print "child test1"
def test2(self):
print "test2"
b = B()
b.test1()
b.test2()
# Output:
Log: 'B.test1' called on Jul 20 2013 - 16:38:11 with arguments: (<__main__.B object at 0x10ddeff50>,), {}
Log: 'A.test1' called on Jul 20 2013 - 16:38:11 with arguments: (<__main__.B object at 0x10ddeff50>,), {}
test1
child test1
Log: 'B.test2' called on Jul 20 2013 - 16:38:11 with arguments: (<__main__.B object at 0x10ddeff50>,), {}
test2
import time
def logged(time_format):
def decorator(func):
def decorated_func(*args, **kwargs):
print "Log: '%s' called on %s with arguments: %s, %s" % (
func.__name__,
time.strftime(time_format),
args,
kwargs
)
return func(*args, **kwargs)
decorated_func.__name__ = func.__name__
return decorated_func
return decorator
@logged("%b %d %Y - %H:%M:%S")
def add(x, y):
return x + y
print add(1, 2)
# Output:
Log: 'add' called on Jul 19 2013 - 21:47:08 with arguments: (1, 2), {}
3
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment