This page is meant to be a central repository of decorator code pieces, whether useful or not <wink>. It is NOT a page to discuss decorator syntax!
Feel free to add your suggestions. Please make sure example code conforms with PEP 8.
Contents
- Creating Well-Behaved Decorators / "Decorator decorator"
- Property Definition
- Memoize
- Alternate memoize as nested functions
- Alternate memoize as dict subclass
- Alternate memoize that stores cache between executions
- Cached Properties
- Retry
- Pseudo-currying
- Creating decorator with optional arguments
- Controllable DIY debug
- Easy adding methods to a class instance
- Counting function calls
- Alternate Counting function calls
- Generating Deprecation Warnings
- Smart deprecation warnings (with valid filenames, line numbers, etc.)
- Ignoring Deprecation Warnings
- Enable/Disable Decorators
- Easy Dump of Function Arguments
- Pre-/Post-Conditions
- Profiling/Coverage Analysis
- Line Tracing Individual Functions
- Synchronization
- Type Enforcement (accepts/returns)
- CGI method wrapper
- State Machine Implementaion
- C++/Java-keyword-like function decorators
- Different Decorator Forms
- Unimplemented function replacement
- Redirects stdout printing to python standard logging.
- Access control
- Events rising and handling
- Singleton
- Asynchronous Call
- Class method decorator using instance
- Another Retrying Decorator
- Logging decorator with specified logger (or default)
- Lazy Thunkify
- Aggregative decorators for generator functions
- Function Timeout
- Collect Data Difference Caused by Decorated Function
Creating Well-Behaved Decorators / "Decorator decorator"
Note: This is only one recipe. Others include inheritance from a standard decorator (link?), the functools @wraps decorator, and a factory function such as Michele Simionato's decorator module which even preserves signature information.
1 def simple_decorator(decorator):
2 '''This decorator can be used to turn simple functions
3 into well-behaved decorators, so long as the decorators
4 are fairly simple. If a decorator expects a function and
5 returns a function (no descriptors), and if it doesn't
6 modify function attributes or docstring, then it is
7 eligible to use this. Simply apply @simple_decorator to
8 your decorator and it will automatically preserve the
9 docstring and function attributes of functions to which
10 it is applied.'''
11 def new_decorator(f):
12 g = decorator(f)
13 g.__name__ = f.__name__
14 g.__doc__ = f.__doc__
15 g.__dict__.update(f.__dict__)
16 return g
17 # Now a few lines needed to make simple_decorator itself
18 # be a well-behaved decorator.
19 new_decorator.__name__ = decorator.__name__
20 new_decorator.__doc__ = decorator.__doc__
21 new_decorator.__dict__.update(decorator.__dict__)
22 return new_decorator
23
24 #
25 # Sample Use:
26 #
27 @simple_decorator
28 def my_simple_logging_decorator(func):
29 def you_will_never_see_this_name(*args, **kwargs):
30 print 'calling {}'.format(func.__name__)
31 return func(*args, **kwargs)
32 return you_will_never_see_this_name
33
34 @my_simple_logging_decorator
35 def double(x):
36 'Doubles a number.'
37 return 2 * x
38
39 assert double.__name__ == 'double'
40 assert double.__doc__ == 'Doubles a number.'
41 print double(155)
Property Definition
These decorators provide a readable way to define properties:
1 import sys
2
3 def propget(func):
4 locals = sys._getframe(1).f_locals
5 name = func.__name__
6 prop = locals.get(name)
7 if not isinstance(prop, property):
8 prop = property(func, doc=func.__doc__)
9 else:
10 doc = prop.__doc__ or func.__doc__
11 prop = property(func, prop.fset, prop.fdel, doc)
12 return prop
13
14 def propset(func):
15 locals = sys._getframe(1).f_locals
16 name = func.__name__
17 prop = locals.get(name)
18 if not isinstance(prop, property):
19 prop = property(None, func, doc=func.__doc__)
20 else:
21 doc = prop.__doc__ or func.__doc__
22 prop = property(prop.fget, func, prop.fdel, doc)
23 return prop
24
25 def propdel(func):
26 locals = sys._getframe(1).f_locals
27 name = func.__name__
28 prop = locals.get(name)
29 if not isinstance(prop, property):
30 prop = property(None, None, func, doc=func.__doc__)
31 else:
32 prop = property(prop.fget, prop.fset, func, prop.__doc__)
33 return prop
34
35 # These can be used like this:
36
37 class Example(object):
38
39 @propget
40 def myattr(self):
41 return self._half * 2
42
43 @propset
44 def myattr(self, value):
45 self._half = value / 2
46
47 @propdel
48 def myattr(self):
49 del self._half
Here's a way that doesn't require any new decorators:
1 class Example(object):
2 @apply # doesn't exist in Python 3
3 def myattr():
4 doc = '''This is the doc string.'''
5
6 def fget(self):
7 return self._half * 2
8
9 def fset(self, value):
10 self._half = value / 2
11
12 def fdel(self):
13 del self._half
14
15 return property(**locals())
16 #myattr = myattr() # works in Python 2 and 3
Yet another property decorator:
1 try:
2 # Python 2
3 import __builtin__ as builtins
4 except ImportError:
5 # Python 3
6 import builtins
7
8 def property(function):
9 keys = 'fget', 'fset', 'fdel'
10 func_locals = {'doc':function.__doc__}
11 def probe_func(frame, event, arg):
12 if event == 'return':
13 locals = frame.f_locals
14 func_locals.update(dict((k, locals.get(k)) for k in keys))
15 sys.settrace(None)
16 return probe_func
17 sys.settrace(probe_func)
18 function()
19 return builtins.property(**func_locals)
20
21 #====== Example =======================================================
22
23 from math import radians, degrees, pi
24
25 class Angle(object):
26 def __init__(self, rad):
27 self._rad = rad
28
29 @property
30 def rad():
31 '''The angle in radians'''
32 def fget(self):
33 return self._rad
34 def fset(self, angle):
35 if isinstance(angle, Angle):
36 angle = angle.rad
37 self._rad = float(angle)
38
39 @property
40 def deg():
41 '''The angle in degrees'''
42 def fget(self):
43 return degrees(self._rad)
44 def fset(self, angle):
45 if isinstance(angle, Angle):
46 angle = angle.deg
47 self._rad = radians(angle)
Memoize
Here's a memoizing class.
1 import collections
2 import functools
3
4 class memoized(object):
5 '''Decorator. Caches a function's return value each time it is called.
6 If called later with the same arguments, the cached value is returned
7 (not reevaluated).
8 '''
9 def __init__(self, func):
10 self.func = func
11 self.cache = {}
12 def __call__(self, *args):
13 if not isinstance(args, collections.Hashable):
14 # uncacheable. a list, for instance.
15 # better to not cache than blow up.
16 return self.func(*args)
17 if args in self.cache:
18 return self.cache[args]
19 else:
20 value = self.func(*args)
21 self.cache[args] = value
22 return value
23 def __repr__(self):
24 '''Return the function's docstring.'''
25 return self.func.__doc__
26 def __get__(self, obj, objtype):
27 '''Support instance methods.'''
28 return functools.partial(self.__call__, obj)
29
30 @memoized
31 def fibonacci(n):
32 "Return the nth fibonacci number."
33 if n in (0, 1):
34 return n
35 return fibonacci(n-1) + fibonacci(n-2)
36
37 print fibonacci(12)
Alternate memoize as nested functions
Here's a memoizing function that works on functions, methods, or classes, and exposes the cache publicly.
Here's a modified version that also respects kwargs.
Alternate memoize as dict subclass
This is an idea that interests me, but it only seems to work on functions:
1 class memoize(dict):
2 def __init__(self, func):
3 self.func = func
4
5 def __call__(self, *args):
6 return self[args]
7
8 def __missing__(self, key):
9 result = self[key] = self.func(*key)
10 return result
11
12 #
13 # Sample use
14 #
15
16 >>> @memoize
17 ... def foo(a, b):
18 ... return a * b
19 >>> foo(2, 4)
20 8
21 >>> foo
22 {(2, 4): 8}
23 >>> foo('hi', 3)
24 'hihihi'
25 >>> foo
26 {(2, 4): 8, ('hi', 3): 'hihihi'}
Alternate memoize that stores cache between executions
Additional information and documentation for this decorator is available on Github.
1 import pickle
2 import collections
3 import functools
4 import inspect
5 import os.path
6 import re
7 import unicodedata
8
9 class Memorize(object):
10 '''
11 A function decorated with @Memorize caches its return
12 value every time it is called. If the function is called
13 later with the same arguments, the cached value is
14 returned (the function is not reevaluated). The cache is
15 stored as a .cache file in the current directory for reuse
16 in future executions. If the Python file containing the
17 decorated function has been updated since the last run,
18 the current cache is deleted and a new cache is created
19 (in case the behavior of the function has changed).
20 '''
21 def __init__(self, func):
22 self.func = func
23 self.set_parent_file() # Sets self.parent_filepath and self.parent_filename
24 self.__name__ = self.func.__name__
25 self.set_cache_filename()
26 if self.cache_exists():
27 self.read_cache() # Sets self.timestamp and self.cache
28 if not self.is_safe_cache():
29 self.cache = {}
30 else:
31 self.cache = {}
32
33 def __call__(self, *args):
34 if not isinstance(args, collections.Hashable):
35 return self.func(*args)
36 if args in self.cache:
37 return self.cache[args]
38 else:
39 value = self.func(*args)
40 self.cache[args] = value
41 self.save_cache()
42 return value
43
44 def set_parent_file(self):
45 """
46 Sets self.parent_file to the absolute path of the
47 file containing the memoized function.
48 """
49 rel_parent_file = inspect.stack()[-1].filename
50 self.parent_filepath = os.path.abspath(rel_parent_file)
51 self.parent_filename = _filename_from_path(rel_parent_file)
52
53 def set_cache_filename(self):
54 """
55 Sets self.cache_filename to an os-compliant
56 version of "file_function.cache"
57 """
58 filename = _slugify(self.parent_filename.replace('.py', ''))
59 funcname = _slugify(self.__name__)
60 self.cache_filename = filename+'_'+funcname+'.cache'
61
62 def get_last_update(self):
63 """
64 Returns the time that the parent file was last
65 updated.
66 """
67 last_update = os.path.getmtime(self.parent_filepath)
68 return last_update
69
70 def is_safe_cache(self):
71 """
72 Returns True if the file containing the memoized
73 function has not been updated since the cache was
74 last saved.
75 """
76 if self.get_last_update() > self.timestamp:
77 return False
78 return True
79
80 def read_cache(self):
81 """
82 Read a pickled dictionary into self.timestamp and
83 self.cache. See self.save_cache.
84 """
85 with open(self.cache_filename, 'rb') as f:
86 data = pickle.loads(f.read())
87 self.timestamp = data['timestamp']
88 self.cache = data['cache']
89
90 def save_cache(self):
91 """
92 Pickle the file's timestamp and the function's cache
93 in a dictionary object.
94 """
95 with open(self.cache_filename, 'wb+') as f:
96 out = dict()
97 out['timestamp'] = self.get_last_update()
98 out['cache'] = self.cache
99 f.write(pickle.dumps(out))
100
101 def cache_exists(self):
102 '''
103 Returns True if a matching cache exists in the current directory.
104 '''
105 if os.path.isfile(self.cache_filename):
106 return True
107 return False
108
109 def __repr__(self):
110 """ Return the function's docstring. """
111 return self.func.__doc__
112
113 def __get__(self, obj, objtype):
114 """ Support instance methods. """
115 return functools.partial(self.__call__, obj)
116
117 def _slugify(value):
118 """
119 Normalizes string, converts to lowercase, removes
120 non-alpha characters, and converts spaces to
121 hyphens. From
122 http://stackoverflow.com/questions/295135/turn-a-string-into-a-valid-filename-in-python
123 """
124 value = unicodedata.normalize('NFKD', value).encode('ascii', 'ignore')
125 value = re.sub(r'[^\w\s-]', '', value.decode('utf-8', 'ignore'))
126 value = value.strip().lower()
127 value = re.sub(r'[-\s]+', '-', value)
128 return value
129
130 def _filename_from_path(filepath):
131 return filepath.split('/')[-1]
Cached Properties
1 #
2 # © 2011 Christopher Arndt, MIT License
3 #
4
5 import time
6
7 class cached_property(object):
8 '''Decorator for read-only properties evaluated only once within TTL period.
9
10 It can be used to create a cached property like this::
11
12 import random
13
14 # the class containing the property must be a new-style class
15 class MyClass(object):
16 # create property whose value is cached for ten minutes
17 @cached_property(ttl=600)
18 def randint(self):
19 # will only be evaluated every 10 min. at maximum.
20 return random.randint(0, 100)
21
22 The value is cached in the '_cache' attribute of the object instance that
23 has the property getter method wrapped by this decorator. The '_cache'
24 attribute value is a dictionary which has a key for every property of the
25 object which is wrapped by this decorator. Each entry in the cache is
26 created only when the property is accessed for the first time and is a
27 two-element tuple with the last computed property value and the last time
28 it was updated in seconds since the epoch.
29
30 The default time-to-live (TTL) is 300 seconds (5 minutes). Set the TTL to
31 zero for the cached value to never expire.
32
33 To expire a cached property value manually just do::
34
35 del instance._cache[<property name>]
36
37 '''
38 def __init__(self, ttl=300):
39 self.ttl = ttl
40
41 def __call__(self, fget, doc=None):
42 self.fget = fget
43 self.__doc__ = doc or fget.__doc__
44 self.__name__ = fget.__name__
45 self.__module__ = fget.__module__
46 return self
47
48 def __get__(self, inst, owner):
49 now = time.time()
50 try:
51 value, last_update = inst._cache[self.__name__]
52 if self.ttl > 0 and now - last_update > self.ttl:
53 raise AttributeError
54 except (KeyError, AttributeError):
55 value = self.fget(inst)
56 try:
57 cache = inst._cache
58 except AttributeError:
59 cache = inst._cache = {}
60 cache[self.__name__] = (value, now)
61 return value
Retry
Call a function which returns True/False to indicate success or failure. On failure, wait, and try the function again. On repeated failures, wait longer between each successive attempt. If the decorator runs out of attempts, then it gives up and returns False, but you could just as easily raise some exception.
1 import time
2 import math
3
4 # Retry decorator with exponential backoff
5 def retry(tries, delay=3, backoff=2):
6 '''Retries a function or method until it returns True.
7
8 delay sets the initial delay in seconds, and backoff sets the factor by which
9 the delay should lengthen after each failure. backoff must be greater than 1,
10 or else it isn't really a backoff. tries must be at least 0, and delay
11 greater than 0.'''
12
13 if backoff <= 1:
14 raise ValueError("backoff must be greater than 1")
15
16 tries = math.floor(tries)
17 if tries < 0:
18 raise ValueError("tries must be 0 or greater")
19
20 if delay <= 0:
21 raise ValueError("delay must be greater than 0")
22
23 def deco_retry(f):
24 def f_retry(*args, **kwargs):
25 mtries, mdelay = tries, delay # make mutable
26
27 rv = f(*args, **kwargs) # first attempt
28 while mtries > 0:
29 if rv is True: # Done on success
30 return True
31
32 mtries -= 1 # consume an attempt
33 time.sleep(mdelay) # wait...
34 mdelay *= backoff # make future wait longer
35
36 rv = f(*args, **kwargs) # Try again
37
38 return False # Ran out of tries :-(
39
40 return f_retry # true decorator -> decorated function
41 return deco_retry # @retry(arg[, ...]) -> true decorator
Pseudo-currying
(FYI you can use functools.partial() to emulate currying (which works even for keyword arguments))
1 class curried(object):
2 '''
3 Decorator that returns a function that keeps returning functions
4 until all arguments are supplied; then the original function is
5 evaluated.
6 '''
7
8 def __init__(self, func, *a):
9 self.func = func
10 self.args = a
11
12 def __call__(self, *a):
13 args = self.args + a
14 if len(args) < self.func.func_code.co_argcount:
15 return curried(self.func, *args)
16 else:
17 return self.func(*args)
18
19
20 @curried
21 def add(a, b):
22 return a + b
23
24 add1 = add(1)
25
26 print add1(2)
