Beruflich Dokumente
Kultur Dokumente
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.
Toggle line numbers
1 def simple_decorator(decorator):
2
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
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
30
31
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:
Toggle line numbers
1 import sys
2
3 def propget(func):
4
locals = sys._getframe(1).f_locals
name = func.__name__
prop = locals.get(name)
8
9
10
11
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
19
20
21
22
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
30
31
32
33
34
@propget
40
def myattr(self):
41
return self._half * 2
42
43
@propset
44
45
self._half = value / 2
46
47
@propdel
48
def myattr(self):
49
del self._half
1 class Example(object):
2
def myattr():
5
6
def fget(self):
return self._half * 2
8
9
10
self._half = value / 2
11
12
def fdel(self):
13
del self._half
14
15
16
return property(**locals())
#myattr = myattr() # works in Python 2 and 3
1 try:
2
# Python 2
4 except ImportError:
5
# Python 3
import builtins
7
8 def property(function):
9
10
func_locals = {'doc':function.__doc__}
11
12
if event == 'return':
13
locals = frame.f_locals
14
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
27
28
29
@property
30
def rad():
31
32
def fget(self):
33
return self._rad
34
35
if isinstance(angle, Angle):
36
angle = angle.rad
37
self._rad = float(angle)
38
39
@property
40
def deg():
41
42
def fget(self):
43
44
45
return degrees(self._rad)
def fset(self, angle):
if isinstance(angle, Angle):
46
47
angle = angle.deg
self._rad = radians(angle)
Memoize
Here's a memoizing class.
Toggle line numbers
1 import collections
2 import functools
3
4 class memoized(object):
5
If called later with the same arguments, the cached value is returned
(not reevaluated).
'''
10
self.func = func
11
self.cache = {}
12
13
14
15
16
return self.func(*args)
17
18
19
if args in self.cache:
return self.cache[args]
else:
20
value = self.func(*args)
21
self.cache[args] = value
22
return value
23
def __repr__(self):
24
25
return self.func.__doc__
26
27
28
29
30 @memoized
31 def fibonacci(n):
32
33
if n in (0, 1):
34
return n
35
36
37 print fibonacci(12)
2 def memoize(obj):
3
cache = obj.cache = {}
4
5
@functools.wraps(obj)
7
8
9
10
1 def memoize(obj):
2
cache = obj.cache = {}
3
4
@functools.wraps(obj)
8
9
10
1 class memoize(dict):
2
3
4
5
6
7
8
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'}
Cached Properties
Toggle line numbers
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
11
12
import random
13
14
15
class MyClass(object):
16
17
@cached_property(ttl=600)
18
def randint(self):
19
20
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
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
29
30
The default time-to-live (TTL) is 300 seconds (5 minutes). Set the TTL to
31
32
33
34
35
36
37
'''
38
39
self.ttl = ttl
40
41
42
self.fget = fget
43
44
self.__name__ = fget.__name__
45
self.__module__ = fget.__module__
46
return self
47
48
49
now = time.time()
50
try:
51
52
53
54
raise AttributeError
except (KeyError, AttributeError):
55
value = self.fget(inst)
56
try:
57
58
59
60
61
cache = inst._cache
except AttributeError:
cache = inst._cache = {}
cache[self.__name__] = (value, now)
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.
Toggle line numbers
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
15
16 tries = math.floor(tries)
17 if tries < 0:
18
19
20 if delay <= 0:
21
22
23 def deco_retry(f):
24
25
26
27
28
29
30
31
32
mtries -= 1
# consume an attempt
33
time.sleep(mdelay) # wait...
34
35
36
37
38
39
40
Pseudo-currying
(FYI you can use functools.partial() to emulate currying (which works even for keyword arguments))
Toggle line numbers
1 class curried(object):
2 '''
3 Decorator that returns a function that keeps returning functions
self.func = func
10
self.args = a
11
12 def __call__(self, *a):
13
args = self.args + a
14
15
16
17
18
19
20 @curried
21 def add(a, b):
22
return a + b
23
24 add1 = add(1)
25
26 print add1(2)
5
6
7
8
9
10
if isinstance(func, type):
11
12
if isFuncArg(*args, **kw):
13
14
15
class_wrapper.__name__ = func.__name__
16
class_wrapper.__module__ = func.__module__
17
return class_wrapper
18
19
@functools.wraps(func)
20
21
if isFuncArg(*args, **kw):
22
23
24
def functor(userFunc):
25
26
27
return functor
28
29
return func_wrapper
Example:
Toggle line numbers
1 @decorator
2 def apply(func, *args, **kw):
3
4
5 @decorator
6 class apply:
7
self.args = args
self.kw = kw
10
11
12
13
14 #
15 # Usage in both cases:
16 #
17 @apply
18 def test():
19
return 'test'
20
21 assert test == 'test'
22
23 @apply(2, 3)
24 def test(a, b):
25
return a + b
26
27 assert test is 5
Note: There is only one drawback: wrapper checks its arguments for single function or class. To avoid wrong
behavior you can use keyword arguments instead of positional, e.g.:
1 @decorator
2 def my_property(getter, *, setter=None, deleter=None, doc=None):
3
1 import sys
2
3 WHAT_TO_DEBUG = set(['io', 'core']) # change to what you need
4
5 class debug:
6
'''
10
11
self.aspects = set(aspects)
12
13
14
15
16
17
18
19
return f_result
20
newf.__doc__ = f.__doc__
21
return newf
22
23
else:
return f
24
25 @debug(['io'])
26 def prn(x):
27
print x
28
29 @debug(['core'])
30 def mult(x, y):
31
return x * y
32
33 prn(mult(2, 2))
1 class Foo:
2
3
def __init__(self):
self.x = 42
4
5 foo = Foo()
6
7 def addto(instance):
8
def decorator(f):
import types
10
11
setattr(instance, f.func_name, f)
12
return f
13
return decorator
14
15 @addto(foo)
16 def print_x(self):
17
print self.x
18
19 # foo.print_x() would print "42"
1 class countcalls(object):
2
3
4
__instances = {}
5
6
self.__f = f
self.__numcalls = 0
countcalls.__instances[f] = self
10
11
12
self.__numcalls += 1
13
14
15
@staticmethod
16
def count(f):
17
18
return countcalls.__instances[f].__numcalls
19
20
@staticmethod
21
def counts():
22
23
1 class countcalls(object):
2
3
4
__instances = {}
5
6
self.__f = f
self.__numcalls = 0
countcalls.__instances[f] = self
10
11
12
self.__numcalls += 1
13
14
15
def count(self):
16
17
return countcalls.__instances[self.__f].__numcalls
18
19
@staticmethod
20
def counts():
21
22
23
24 #example
25
26 @countcalls
27 def f():
28
29
30 @countcalls
31 def g():
32
33
34 f()
35 f()
36 f()
37 print f.count() # prints 3
38 print countcalls.counts() # same as f.counts() or g.counts()
39 g()
40 print g.count() # prints 1
1 import warnings
2
3 def deprecated(func):
4
8
9
10
11
new_func.__name__ = func.__name__
12
new_func.__doc__ = func.__doc__
13
new_func.__dict__.update(func.__dict__)
14
return new_func
15
16 # === Examples of use ===
17
18 @deprecated
19 def some_old_function(x,y):
20
return x + y
21
22 class SomeClass:
23
@deprecated
24
25
return x + y
1 import warnings
2 import functools
3
4
5 def deprecated(func):
9
10
@functools.wraps(func)
11
12
warnings.warn_explicit(
13
14
category=DeprecationWarning,
15
filename=func.func_code.co_filename,
16
lineno=func.func_code.co_firstlineno + 1
17
18
19
return new_func
20
21
22 ## Usage examples ##
23 @deprecated
24 def my_func():
25
pass
26
27 @other_decorators_must_be_upper
28 @deprecated
29 def my_func():
30
pass
1 import warnings
2
3 def ignore_deprecation_warnings(func):
4
occurring in a function.'''
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=DeprecationWarning)
10
new_func.__name__ = func.__name__
11
new_func.__doc__ = func.__doc__
12
new_func.__dict__.update(func.__dict__)
13
return new_func
14
20
category=DeprecationWarning)
21
22 class SomeClass:
23
@ignore_deprecation_warnings
24
def some_method_raising_deprecation_warning():
25
26
category=DeprecationWarning)
Enable/Disable Decorators
Toggle line numbers
1 def unchanged(func):
2
return func
4
5 def disabled(func):
6
def empty_func(*args,**kargs):
8
9
pass
return empty_func
10
11 # define this as equivalent to unchanged, for nice symmetry with disabled
12 enabled = unchanged
13
14 #
15 # Sample use
16 #
17
18 GLOBAL_ENABLE_FLAG = True
19
20 state = enabled if GLOBAL_ENABLE_FLAG else disabled
21 @state
22 def special_function_foo():
23
1 def dump_args(func):
2
"This decorator dumps out the arguments passed to a function before calling it"
argnames = func.func_code.co_varnames[:func.func_code.co_argcount]
fname = func.func_name
5
6
def echo_func(*args,**kwargs):
'%s=%r' % entry
10
11
12
return echo_func
13
14 @dump_args
15 def f1(a,b,c):
16
print a + b + c
17
18 f1(1, 2, 3)
Pre-/Post-Conditions
Toggle line numbers
1 '''
2 Provide pre-/postconditions as function decorators.
3
4 Example usage:
5
6 >>> def in_ge20(inval):
7 ...
8 ...
9 >>> def out_lt30(retval, inval):
10 ...
11 ...
12 >>> @precondition(in_ge20)
13 ... @postcondition(out_lt30)
14 ... def inc(value):
15 ... return value + 1
16 ...
17 >>> inc(5)
18 Traceback (most recent call last):
19
...
...
...
60
61 def postcondition(postcondition, use_conditions=DEFAULT_ON):
62
63
64 class conditions(object):
65
66
67
68
69
70
71
self.__precondition = pre
72
self.__postcondition = post
73
74
75
76
pres = set((self.__precondition,))
77
posts = set((self.__postcondition,))
78
79
80
81
pres.add(function._pre)
82
posts.add(function._post)
83
function = function._func
84
85
# filter out None conditions and build pairs of pre- and postconditions
86
87
88
# add a wrapper for each pair (note that 'conditions' may be empty)
89
90
91
92
return function
93
94 class FunctionWrapper(object):
95
96
self._pre = precondition
97
self._post = postcondition
98
self._func = function
99
100
101
precondition = self._pre
102
postcondition = self._post
103
104
if precondition:
105
precondition(*args, **kwargs)
106
107
if postcondition:
108
109
110
111 def __test():
112
import doctest
113
doctest.testmod()
114
115 if __name__ == "__main__":
116
__test()
Profiling/Coverage Analysis
The code and examples are a bit longish, so I'll include a link instead: http://mg.pov.lt/blog/profiling.html
1 import sys
2 import os
3 import linecache
4
5 def trace(f):
6
7
8
9
10
11
12
13
14
filename = frame.f_code.co_filename
15
lineno = frame.f_lineno
16
17
bname = os.path.basename(filename)
18
19
lineno,
20
linecache.getline(filename, lineno)),
21
return localtrace
22
23
24
sys.settrace(globaltrace)
25
26
sys.settrace(None)
27
return result
28
29
return _f
Synchronization
Synchronize two (or more) functions on a given lock.
Toggle line numbers
1 def synchronized(lock):
2
'''Synchronization decorator.'''
3
4
5
def wrap(f):
def new_function(*args, **kw):
lock.acquire()
try:
8
9
10
11
12
lock.release()
return new_function
return wrap
13
14 # Example usage:
15
16 from threading import Lock
17 my_lock = Lock()
18
19 @synchronized(my_lock)
20 def critical1(*args):
21
22
pass
23
24 @synchronized(my_lock)
25 def critical2(*args):
26
27
pass
1 '''
2 One of three degrees of enforcement may be specified by passing
5 #!python
6 -- MEDIUM: Print warning message to stderr. (Default)
7
12
>>>
13
14
... @returns(float)
15
16
...
17
...
18
19
TypeWarning: 'average' method accepts (int, int, int), but was given
20
21
15.25
22
23
24
15
return (x + y + z) / 2
25
26 Needed to cast params as floats in function def (or simply divide by 2.0).
27
28
29
30
31
32
...
33
...
34
...
35
>>> fib(5.3)
36
37
38
...
TypeError: 'fib' method accepts (int), but was given (float)
39
40 '''
41 import sys
42
43 def accepts(*types, **kw):
44
45
46
47
Parameters:
48
49
50
51
52
debug = ( 0 | 1 | 2 )
53
54
'''
55
if not kw:
56
57
debug = 1
58
59
60
61
else:
debug = kw['debug']
try:
def decorator(f):
62
def newf(*args):
63
if debug is 0:
64
return f(*args)
65
66
67
if argtypes != types:
68
69
if debug is 1:
70
71
elif debug is 2:
72
73
return f(*args)
74
newf.__name__ = f.__name__
75
return newf
76
return decorator
77
78
79
80
81
82
83 def returns(ret_type, **kw):
84
85
86
87
Parameters:
88
89
90
91
92
debug=(0 | 1 | 2)
93
'''
94
try:
95
if not kw:
96
97
debug = 1
98
99
100
101
else:
debug = kw['debug']
def decorator(f):
def newf(*args):
102
result = f(*args)
103
if debug is 0:
104
return result
105
res_type = type(result)
106
if res_type != ret_type:
107
108
if debug is 1:
109
110
111
112
113
newf.__name__ = f.__name__
114
return newf
115
return decorator
116
117
118
119
120
121 def info(fname, expected, actual, flag):
122
123
124
125
126
127
128
return msg
Handles HTML boilerplate at top and bottom of pages returned from CGI methods. Works with the cgi
module. Now your request handlers can just output the interesting HTML, and let the decorator deal with all
the top and bottom clutter.
(Note: the exception handler eats all exceptions, which in CGI is no big loss, since the program runs in its
separate subprocess. At least here, the exception contents will be written to the output page.)
Toggle line numbers
1 class CGImethod(object):
2
3
4
5
6
print "<HTML>"
print "<HEAD><TITLE>{}</TITLE></HEAD>".format(self.title)
10
print "<BODY>"
11
try:
12
fn(*args)
13
except Exception, e:
14
15
print e
16
17
print "</BODY></HTML>"
18
19
return wrapped_fn
20
21 @CGImethod("Hello with Decorator")
22 def say_hello():
23
# Create Statedefn object for each state you need to keep track of.
# the name passed to the constructor becomes a StateVar member of the current class.
10
gstate = StateTable("gstate")
11
tstate = StateTable("turtle")
12
13
14
# must call init method of class's StateTable object. to initialize state variable
15
self.gstate.initialize(self)
16
self.tstate.initialize(self)
17
self.mname = name
18
self.a_count = 0
19
self.b_count = 0
20
self.c_count = 0
21
22
23
@event_handler(gstate)
24
25
26
@event_handler(gstate)
27
28
29
@event_handler(gstate)
30
31
32
@event_handler(tstate)
33
34
35
36
37
def _event_a_hdlr1(self):
38
39
self.a_count += 1
40
def _event_b_hdlr1(self):
41
42
self.b_count += 1
43
44
45
self.c_count += 3*val
46
47
def _event_a_hdlr2(self):
48
49
self.a_count += 10
50
# here we brute force the tstate to on, leave & enter functions called if state changes.
51
# turtle is object's state variable for tstate, comes from constructor argument
52
self.turtle.set_state(self, self._t_on)
53
def _event_b_hdlr2(self):
54
55
self.b_count += 10
56
57
58
self.c_count += 2*val
59
60
def _event_a_hdlr3(self):
61
self.a_count += 100
62
63
def _event_b_hdlr3(self):
64
65
self.b_count += 100
66
67
68
self.gstate.next_state = self._state2
69
70
71
self.c_count += 5*val
72
73
# Associate the handlers with a state. The first argument is a list of methods.
74
# One method for each event_handler decorated function of gstate. Order of methods
75
# in the list correspond to order in which the Event Handlers were declared.
76
# Second arg is the name of the state. Third argument is to be come a list of the
77
# next states.
78
79
80
81
82
83
84
None,
"One"))
"One",
"Two"))
85
86
87
88
89
@on_enter_function(gstate)
90
def _enter_gstate(self):
91
92
@on_leave_function(tstate)
93
def _leave_tstate(self):
94
95
96
97
def _toggle_on(self):
98
99
100
def _toggle_off(self):
101
102
103
104
105
["On"])
_t_on = tstate.state("On", [_toggle_off],
106
["Off"])
107
108
109 def main():
110
big_machine = MyMachine("big")
111
lil_machine = MyMachine("lil")
112
113
big_machine.event_a()
114
lil_machine.event_a()
115
big_machine.event_a()
116
lil_machine.event_a()
117
big_machine.event_b()
118
lil_machine.event_b()
119
big_machine.event_c(4)
120
lil_machine.event_c(2)
121
big_machine.event_c(1)
122
lil_machine.event_c(3)
123
big_machine.event_b()
124
lil_machine.event_b()
125
big_machine.event_a()
126
lil_machine.event_a()
127
big_machine.event_a()
128
129
big_machine.toggle()
130
big_machine.toggle()
131
big_machine.toggle()
132
133
lil_machine.event_a()
134
big_machine.event_b()
135
lil_machine.event_b()
136
big_machine.event_c(3)
137
big_machine.event_a()
138
lil_machine.event_c(2)
139
lil_machine.event_a()
140
big_machine.event_b()
141
lil_machine.event_b()
142
big_machine.event_c(7)
143
lil_machine.event_c(1)
144
145
146
147
148
149
150 main()
1#
2 # Support for State Machines. ref - Design Patterns by GoF
3 # Many of the methods in these classes get called behind the scenes.
4#
5 # Notable exceptions are methods of the StateVar class.
6#
7 # See example programs for how this module is intended to be used.
8#
9 class StateMachineError(Exception):
10
11
12
13 class StateVar(object):
14
15
self._current_state = initial_state
16
self.next_state = initial_state
17
18
19
'''
20
21
'''
22
self.next_state = new_state
23
self.__to_next_state(owner)
24
25
26
'''
27
The low-level state change function which calls leave state & enter state functions as
28
needed.
29
30
LeaveState and EnterState functions are called as needed when state transitions.
31
'''
32
33
if hasattr(self._current_state, "leave"):
34
self._current_state.leave(owner)
35
36
self.leave(owner)
37
self._current_state = self.next_state
38
if hasattr(self._current_state, "enter"):
39
self._current_state.enter(owner)
40
41
self.enter(owner)
42
43
44
'''
45
Returns the owning class's method for handling an event for the current state.
46
47
'''
48
vf = self._current_state.get_fe(func_name)
49
return vf
50
51
def name(self):
52
'''
53
54
'''
55
return self._current_state.name
56
57 class STState(object):
58
59
self.name = state_name
60
self.fctn_dict = {}
61
62
63
dictionary = self.fctn_dict
64
if not next_states:
65
66
67
68
else:
69
70
71
72
self.fctn_dict = dictionary
73
74
75
return self.fctn_dict[fctn_name]
76
77
78
''' Changes second dict value from name of state to actual state.'''
79
for de in self.fctn_dict.values():
80
next_state_name = de[1]
81
if next_state_name:
82
if next_state_name in state_dict:
83
de[1] = state_dict[next_state_name]
84
else:
85
86
87
88 class StateTable(object):
89
'''
90
Magical class to define a state machine, with the help of several decorator functions
91
which follow.
92
'''
93
94
self.machine_var = declname
95
self._initial_state = None
96
self._state_list = {}
97
self._event_list = []
98
self.need_initialize = 1
99
100
101
'''
102
Initializes the parent class's state variable for this StateTable class.
103
Must call this method in the parent' object's __init__ method. You can have
104
Multiple state machines within a parent class. Call this method for each
105
'''
106
statevar= StateVar(self._initial_state)
107
108
if hasattr(self, "enter"):
109
110
111
112
statevar.enter = self.enter
if hasattr(self, "leave"):
statevar.leave = self.leave
#Magic happens here - in the 'next state' table, translate names into state objects.
113
if self.need_initialize:
114
115
xstate.map_next_states(self._state_list)
116
self.need_initialize = 0
117
118
119
'''
120
This is used to define a state. the event handler list is a list of functions that
121
are called for corresponding events. name is the name of the state.
122
'''
123
state_table_row = STState(name)
124
if len(event_hdlr_list) != len(self._event_list):
125
raise StateMachineError('Mismatch between number of event handlers and the methods specified for
the state.')
126
127
128
129
if self._initial_state is None:
130
self._initial_state = state_table_row
131
self._state_list[name] = state_table_row
132
return state_table_row
133
134
135
state_table_row = STState(name)
136
if len(event_hdlr_list) != len(self._event_list):
137
raise StateMachineError('Mismatch between number of event handlers and the methods specified for
the state.')
138
139
raise StateMachineError('Mismatch between number of event handlers and the next states specified
142
143
if self._initial_state is None:
144
self._initial_state = state_table_row
145
self._state_list[name] = state_table_row
146
return state_table_row
147
148
149
'''
150
Informs the class of an event handler to be added. We just need the name here. The
151
function name will later be associated with one of the functions in a list when a state is defined.
152
'''
153
self._event_list.append(func_name)
154
155 # Decorator functions ...
156 def event_handler(state_class):
157
'''
158
159
'''
160
def wrapper(func):
161
state_class._StateTable__add_ev_hdlr(func.__name__)
162
163
164
165
166
state_var.next_state = next_state
167
168
state_var._StateVar__to_next_state(self)
169
return rv
170
return obj_call
171
return wrapper
172
173 def on_enter_function(state_class):
174
'''
175
Declare that this method should be called whenever a new state is entered.
176
'''
177
def wrapper(func):
178
state_class.enter = func
179
return func
180
return wrapper
181
182 def on_leave_function(state_class):
183
'''
184
185
'''
186
def wrapper(func):
187
state_class.leave = func
188
return func
189
return wrapper