Best way to ensure user calls methods in correct order?

Steve D'Aprano steve+python at pearwood.info
Thu Jun 22 10:40:18 EDT 2017


On Thu, 22 Jun 2017 11:53 pm, 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.

Don't do that. It's fragile and an anti-pattern. Your methods have too much
coupling. If c() relies on b() being called first, then either b() or c()
aren't good methods. They don't do enough:

- calling b() alone doesn't do enough, so you have to call c() next to get the
job done;

- calling c() alone doesn't do enough, because it relies on b() being called
first.


There are a very few exceptions to this rule of thumb, such as opening
connections to databases or files or similar. They are mostly enshrined from
long practice, or justified by low-level APIs (that's how file systems and
databases work, and it's not practical to change that). But you should try very
hard to avoid creating new examples.

Of course, I'm talking about your *public* methods. Private methods can be a bit
more restrictive, since it's only *you* who suffers if you do it wrong.

Ideally, your methods should be written in a functional style with as little
shared state as possible. (Shared state introduces coupling between components,
and excessive coupling is *the* ultimate evil in programming.) With no shared
state, your methods are trivial:

def a(self, arg):
    return something

def b(self, arg):
    # relies on a
    x = self.a(arg)
    return process(x)

def c(self, arg):
    # relies on b
    x = self.b(arg)
    return process(x)
    

With shared state, it becomes more horrible, but at least your users are shared
the pain. (*You* on the other hand, are not -- that's your punishment for
writing in a Java-like style with lots of shared state.)


def a(self):
    self.data = something
    self.step = 1

def b(self):
    # relies on a
    assert self.step >= 0
    if self.step == 0:
        self.a()
    if self.step == 1:
        self.data = process(self.data)
        self.step = 2
    else:
        assert self.step > 1
        # do nothing?
        # or raise?

def c(self):
    # relies on b
    assert self.step >= 0
    if self.step < 2:
        self.b()
    if self.step == 2:
        self.data = process(self.data)
        self.step = 3
    else:
        assert self.step > 1
        # do nothing?
        # or raise?


This is messy enough if there is only a single chain of correct calls:

# you must call a, b, c, d in that order

If you can call the methods in lots of different ways:

# you can call a, b, c, d;
# or a, b, d;
# or a, d, e, f
# or g, d, e
# ...

it becomes horrible. In that case, I'd say just document what the methods do and
let the user deal with the mess.



>         1) Most importantly, am I being stupid? I.e. is there some obviously
> better way to handle this sort of thing?

I wouldn't say "stupid" as such, but since you used the word, yes :-)


>         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.?

If you want the user to call methods a, b, c, d in that order, provide then with
a single method "run" that calls a, b, c, d in that order, and tell them to
call "run".




-- 
Steve
“Cheer up,” they said, “things could be worse.” So I cheered up, and sure
enough, things got worse.




More information about the Python-list mailing list