Best way to ensure user calls methods in correct order?
Peter Otten
__peter__ at web.de
Thu Jun 22 10:31:15 EDT 2017
Thomas Nyberg wrote:
> I have a situation in which I want a user to call methods in a certain
> order and to force the re-calling of methods "down-stream" if upstream
> methods are called again. An example of this sort of thing would be a
> pipeline where calling methods again invalidates the results of methods
> called afterwards and you'd like to warn the user of that.
>
> Here is a very simplified example:
>
> ordered_consistency.py
> -----------------------------------------------
> class ConsistencyError(Exception):
> pass
>
> class C:
> def __init__(self):
> self._a_dirty = self._b_dirty = self._c_dirty = True
>
> def a(self):
> self._a_dirty = self._b_dirty = self._c_dirty = True
> print("Calling a()...")
> self._a_dirty = False
>
> def b(self):
> if self._a_dirty:
> raise ConsistencyError("Re-run a() before calling b()!")
> self._b_dirty = self._c_dirty = True
> print("Calling b()...")
> self._b_dirty = False
>
> def c(self):
> if self._b_dirty:
> raise ConsistencyError("Re-run b() before calling c()!")
> self._c_dirty = True
> print("Calling c()...")
> self._c_dirty = False
>
> def d(self):
> if self._c_dirty:
> raise ConsistencyError("Re-run c() before calling d()!")
> print("Calling d()...")
>
> c = C()
> # This is fine:
> c.a()
> c.b()
> c.c()
> c.d()
> # This is also fine (with same class instantiation!)
> c.c()
> c.d()
> # This throws an error:
> c.b()
> c.d()
> -----------------------------------------------
>
> Here's what you get when calling it:
> -----------------------------------------------
> $ python3 ordered_methods.py
> Calling a()...
> Calling b()...
> Calling c()...
> Calling d()...
> Calling c()...
> Calling d()...
> Calling b()...
> Traceback (most recent call last):
> File "ordered_methods.py", line 43, in <module>
> c.d()
> File "ordered_methods.py", line 29, in d
> raise ConsistencyError("Re-run c() before calling d()!")
> __main__.ConsistencyError: Re-run c() before calling d()!
> -----------------------------------------------
>
> My solution seems to work, but has a large amount of repetition and
> errors will certainly be introduced the first time anything is changed.
> I have the following questions:
>
> 1) Most importantly, am I being stupid? I.e. is there some obviously
> better way to handle this sort of thing?
> 2) Is there an out of the box solution somewhere that enforces this
> that I've never seen before?
> 3) If not, can anyone here see some sort of more obvious way to do this
> without all the repetition in dirty bits, error messages, etc.?
>
> I presume a decorator could be used to do some sort of "order
> registration", but I figure I might as well ask before I re-invent a
> hexagonal wheel. Thanks a lot for any help!
If the order is linear like it seems to be the following might work:
$ cat inorder.py
import itertools
class ConsistencyError(Exception):
pass
class InOrder:
def __init__(self):
self.indices = itertools.count(1)
def __call__(self, method):
index = next(self.indices)
def wrapper(self, *args, **kw):
print("index", index, "state", self.state)
if index - self.state > 1:
raise ConsistencyError
result = method(self, *args, **kw)
self.state = index
return result
return wrapper
class C:
def __init__(self):
self.state = 0
inorder = InOrder()
@inorder
def a(self):
print("Calling a()...")
@inorder
def b(self):
print("Calling b()...")
@inorder
def c(self):
print("Calling c()...")
@inorder
def d(self):
print("Calling d()...")
$ python3 -i inorder.py
>>>
>>> c = C()
>>> c.a()
index 1 state 0
Calling a()...
>>> c.b()
index 2 state 1
Calling b()...
>>> c.a()
index 1 state 2
Calling a()...
>>> c.c()
index 3 state 1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "inorder.py", line 18, in wrapper
raise ConsistencyError
__main__.ConsistencyError
>>> c.b()
index 2 state 1
Calling b()...
>>> c.c()
index 3 state 2
Calling c()...
More information about the Python-list
mailing list