1 | from timeit import Timer
|
---|
2 |
|
---|
3 | # A control model
|
---|
4 | setup_a = '''
|
---|
5 | class Model(object):
|
---|
6 | pass
|
---|
7 | m = Model()
|
---|
8 | '''
|
---|
9 |
|
---|
10 | # A model which has a __setattr__ which does nothing
|
---|
11 | setup_b = '''
|
---|
12 | class Model(object):
|
---|
13 | def __setattr__(self, name, value):
|
---|
14 | super(Model, self).__setattr__(name, value)
|
---|
15 | m = Model()
|
---|
16 | '''
|
---|
17 | setup_b2 = '''
|
---|
18 | class Model(object):
|
---|
19 | def __setattr__(self, name, value):
|
---|
20 | self.__dict__[name] = value
|
---|
21 | m = Model()
|
---|
22 | '''
|
---|
23 |
|
---|
24 | # A model which has a __setattr__ which matches this patch
|
---|
25 | setup_c = '''
|
---|
26 | class Model(object):
|
---|
27 | def __setattr__(self, name, value):
|
---|
28 | if name != '_modified_attrs' and (not hasattr(self, name) or
|
---|
29 | value != getattr(self, name)):
|
---|
30 | if hasattr(self, '_modified_attrs'):
|
---|
31 | if name not in self._modified_attrs:
|
---|
32 | self._modified_attrs.add(name)
|
---|
33 | else:
|
---|
34 | self._modified_attrs = set((name,))
|
---|
35 | super(Model, self).__setattr__(name, value)
|
---|
36 | m = Model()
|
---|
37 | '''
|
---|
38 |
|
---|
39 | # Optimized for speed, and dropped the value check as Brian suggested
|
---|
40 | setup_d = '''
|
---|
41 | class Model(object):
|
---|
42 | def __setattr__(self, name, value):
|
---|
43 | try:
|
---|
44 | if name not in self._modified_attrs:
|
---|
45 | self._modified_attrs.add(name)
|
---|
46 | except AttributeError:
|
---|
47 | if name != '_modified_attrs':
|
---|
48 | self._modified_attrs = set((name,))
|
---|
49 | super(Model, self).__setattr__(name, value)
|
---|
50 | m = Model()
|
---|
51 | '''
|
---|
52 |
|
---|
53 | # Hyper-optimized
|
---|
54 | setup_h = '''
|
---|
55 | class Model(object):
|
---|
56 | def __init__(self):
|
---|
57 | self._reset_modified_attrs()
|
---|
58 | def _reset_modified_attrs(self):
|
---|
59 | self.__dict__['_modified_attrs'] = []
|
---|
60 | def __setattr__(self, name, value):
|
---|
61 | if name not in self._modified_attrs:
|
---|
62 | self._modified_attrs.append(name)
|
---|
63 | self.__dict__[name] = value
|
---|
64 | m = Model()
|
---|
65 | '''
|
---|
66 |
|
---|
67 | # A model which has uses properties to track changes
|
---|
68 | # Bonus: only field access would be tracked this way
|
---|
69 | setup_f = '''
|
---|
70 | def track_property(klass, name):
|
---|
71 | p = '_p_%s' % name
|
---|
72 | def fget(o):
|
---|
73 | getattr(o, p)
|
---|
74 | def fset(o, value):
|
---|
75 | set_modified(o)
|
---|
76 | setattr(o, p, value)
|
---|
77 | def set_modified(o):
|
---|
78 | try:
|
---|
79 | if name not in o._modified_attrs:
|
---|
80 | o._modified_attrs.add(name)
|
---|
81 | except AttributeError:
|
---|
82 | o._modified_attrs = set((name,))
|
---|
83 | setattr(klass, name, property(fget, fset))
|
---|
84 | class Model(object):
|
---|
85 | def __init__(self):
|
---|
86 | track_property(self.__class__, 'an_attribute')
|
---|
87 | track_property(self.__class__, 'another_attribute')
|
---|
88 | m = Model()
|
---|
89 | '''
|
---|
90 |
|
---|
91 | # A model which has uses a new style attribute object
|
---|
92 | # Bonus: only field access would be tracked this way
|
---|
93 | setup_g = '''
|
---|
94 | def track_property(klass, name):
|
---|
95 | p = '_p_%s' % name
|
---|
96 | def set_modified(o):
|
---|
97 | try:
|
---|
98 | if name not in o._modified_attrs:
|
---|
99 | o._modified_attrs.add(name)
|
---|
100 | except AttributeError:
|
---|
101 | o._modified_attrs = set((name,))
|
---|
102 | class Attr(object):
|
---|
103 | def __get__(self, instance, value):
|
---|
104 | getattr(instance, p)
|
---|
105 | def __set__(self, instance, value):
|
---|
106 | set_modified(instance)
|
---|
107 | setattr(instance, p, value)
|
---|
108 | setattr(klass, name, Attr())
|
---|
109 | class Model(object):
|
---|
110 | def __init__(self):
|
---|
111 | track_property(self.__class__, 'an_attribute')
|
---|
112 | track_property(self.__class__, 'another_attribute')
|
---|
113 | m = Model()
|
---|
114 | '''
|
---|
115 |
|
---|
116 | def timed(t, control=None):
|
---|
117 | if control is None:
|
---|
118 | return '%.3f' % (t)
|
---|
119 | return '%.3f (%.1fx)' % (t, t/control)
|
---|
120 |
|
---|
121 | def do_test(test, extra_setup=''):
|
---|
122 | control = Timer(test, setup_a+extra_setup).timeit()
|
---|
123 | print "Control: ", timed(control)
|
---|
124 | print "noop: ", timed(Timer(test, setup_b+extra_setup).timeit(), control)
|
---|
125 | print "patch: ", timed(Timer(test, setup_c+extra_setup).timeit(), control)
|
---|
126 | print "optimized:", timed(Timer(test, setup_d+extra_setup).timeit(), control)
|
---|
127 | print "noop-h-op:", timed(Timer(test, setup_b2+extra_setup).timeit(), control)
|
---|
128 | print "h-optimiz:", timed(Timer(test, setup_h+extra_setup).timeit(), control)
|
---|
129 | print "property: ", timed(Timer(test, setup_f+extra_setup).timeit(), control)
|
---|
130 | print "new-attr: ", timed(Timer(test, setup_g+extra_setup).timeit(), control)
|
---|
131 |
|
---|
132 | print "Single Assignment"
|
---|
133 | do_test('m.an_attribute="test"')
|
---|
134 |
|
---|
135 | print
|
---|
136 | print "Double Assignment"
|
---|
137 | do_test('m.an_attribute="test";m.another_attribute="test"')
|
---|
138 |
|
---|
139 | print
|
---|
140 | print "Retreiving"
|
---|
141 | do_test('m.an_attribute', extra_setup='m.an_attribute="test"')
|
---|