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