Welcome, guest | Sign In | My Account | Store | Cart

This decorator runs a function/method the first time called, and on any subsequent calls when some user-defined metric changes. The metric function provided to the decorator is called every time, but the function being decorated is only called on the first call and when the metric changes.

Python, 80 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
def func_onchange(metric):
    def _inner_onchange(func):
        "A decorator that runs a function only when a generic metric changes."
        def decorated(*args, **kwargs):
            try:
                mresult = metric(*args, **kwargs)
                if decorated._last_metric != mresult:
                    decorated._last_metric = mresult
                    decorated._last_result = func(*args, **kwargs)
            except AttributeError:
                decorated._last_metric = mresult    
                decorated._last_result = func(*args, **kwargs)

            return decorated._last_result
        return decorated
    return _inner_onchange
    
def method_onchange(metric):
    def _inner_onchange(method):
        "A decorator that runs a method only when a generic metric changes."
        met_name = "_%s_last_metric" % id(method)
        res_name = "_%s_last_result" % id(method)
        def decorated(self, *args, **kwargs):
            try:
                mresult = metric(*args, **kwargs)
                if getattr(decorated, met_name)  != mresult:
                    setattr(decorated, met_name, mresult)
                    setattr(decorated, res_name, method(self, *args, **kwargs))
            except AttributeError:
                setattr(decorated, met_name, mresult)
                setattr(decorated, res_name, method(self, *args, **kwargs))

            return getattr(decorated, res_name)
        return decorated
    return _inner_onchange
    

# simple check to see if file changed
def cheezy_file_metric(filename):
    import os
    return os.stat(filename)

# Function example, will only read file contents when file stats change
@func_onchange(cheezy_file_metric)
def get_filecontents(filename):
    print 'reading "%s" contents' % filename
    return file(filename).read()

file('test.txt', 'w').write('original content')
print '-'*60
print get_filecontents('test.txt')
print '-'*60
print get_filecontents('test.txt')
file('test.txt', 'w').write('new and improved content!')
print '-'*60
print get_filecontents('test.txt')
print '-'*60
print get_filecontents('test.txt')


print '~'*60

# Method example, will only read file contents when file stats change
class FileReadThing:
    @method_onchange(cheezy_file_metric)
    def get_filecontents(self, filename):
        print 'reading "%s" contents' % filename
        return file(filename).read()

f = FileReadThing()
file('test.txt', 'w').write('original content')
print '-'*60
print f.get_filecontents('test.txt')
print '-'*60
print f.get_filecontents('test.txt')
file('test.txt', 'w').write('new and improved content!')
print '-'*60
print f.get_filecontents('test.txt')
print '-'*60
print f.get_filecontents('test.txt')

I needed to implement a "reload when file content/time changes" feature in a couple of places in an application at work, and I was about to do it the copy/paste/edit way (silly, I know) until I happened to see Ori Peleg's recipe (http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/425445) that appeared on the Daily Python-URL yesterday. Since it didn't seem like it would be hard to build some general-purpose "on change" decorators from Ori's recipe, I tweaked on it for a bit, and this is the result.

It seems to me that there should be some way to remove the duplication of code between the function and method versions of the decorator, and the duplication of those two lines within the try/except blocks, but I don't have time to figure that out at the moment. Please comment if you can see an elegant way to do it.

Created by Alan McIntyre on Fri, 24 Jun 2005 (PSF)
Python recipes (4591)
Alan McIntyre's recipes (2)

Required Modules

Other Information and Tasks