1

I started out with something like this:

class Client (object):
    def __init__ (self):
        self.state = None
    def open (self, destination):
        if self.state not in [None]: raise error ...
        ... open stuff ...
        self.state = 'Open'
    def handle (self):
        if self.state not in ['Open'] raise error ...
        ... handle stuff ...
    def close (self):
        if self.state not in ['Open'] raise error ...
        ... close stuff ...
        self.state = None

(I'm not happy about having separate __init__() and open() methods but the stuff I call requires things to be this way, I'm afraid. It's not central to my question anyhow.)

Now, as the number of methods and states will be growing, I thought I should refactor into something like this:

    @states([None])
    def open (self, destination):
        ... open stuff ...

and similarly for the other methods. Based on e.g. this classic SO answer I came up with the following definition for the decorator:

from functools import wraps

def states (statelist):
    def decorator (f):
        @wraps(f)   # In order to preserve docstrings, etc.
        def wrapped (self, *args, **kwargs):
            if self.state not in statelist: raise error ...
            return f(self, *args, **kwargs)
        return wrapped
    return decorator

This is fairly complex, and also has the problem that it will not be inherited by derived classes (my solution was to simply make it a global). My question is: is this the minimal, idiomatic solution to this problem, or am I doing something wrong? This is the first time I'm trying to define my own decorator. The various references I have found (including this one which pointed me to wraps) seem to suggest to my uninformed self that this is really how it has to be. (Or is there a nifty library which encapsulates this sort of contortion for me? Had a quick glance at functools but I can't say I understand the documentation really, and anyway, the nifty stuff seems to be >= 2.6 whereas I need to support Python 2.5 for a little while still ...)

Community
  • 1
  • 1
tripleee
  • 175,061
  • 34
  • 275
  • 318
  • I'm thinking that you might want a descriptor instead, but I can't put it together in my head quite yet... – Ignacio Vazquez-Abrams Sep 25 '12 at 19:26
  • 1
    You can use [the decorator module](http://pypi.python.org/pypi/decorator) to make it slightly shorter, but it is fine this way. – Jochen Ritzel Sep 25 '12 at 19:40
  • BTW if you're not happy with decorating every overridden method, consider using a metaclass that would take a description of allowed states for each method and add state-checking code to each. This is best if the sets of states and methods do not change but methods' implementations do. – 9000 Sep 25 '12 at 19:41
  • @JochenRitzel: thanks for the pointer to the decorator module. It helps me understand the `functools` documentation, too ... I think. – tripleee Sep 25 '12 at 20:28

3 Answers3

2

Yes - this solution is quite close to as simple as you can get - and it is readable.

You should go for it - the alternative is to learn about "Aspect Oriented" programing, and check available Python libraries for using aspect orientation - the use case for A.O. is more or less this: adding boiler plate code to all methods that have a common trait. (And in Python it is just a matter of using a proper module, no need for using an alternate compiler for a super-set of the language as happen with Java)

jsbueno
  • 99,910
  • 10
  • 151
  • 209
  • 1
    I agree, what you have is good. If you want to eliminate brackets you can `def states(*statelist)` and then decorate with `@states('Open', 'New')` etc. – wberry Sep 25 '12 at 20:58
2

I would modify it to:

def states(*states):
    def decorator (f):
        @wraps(f)   # In order to preserve docstrings, etc.
        def wrapped(self, *args, **kwargs):
            if self.state not in states: raise error ...
            return f(self, *args, **kwargs)
        return wrapped
    return decorator

Which saves you typing the square brackets.

Eric
  • 95,302
  • 53
  • 242
  • 374
1

You could use the State Pattern:

class AlreadyClosedError(Exception): pass
class AlreadyOpenError(Exception): pass

class Client(object):

    def __init__(self):
        self._change_state(Closed)

    def _change_state(self, state):
        self.__class__ = state


class Closed(Client):

    def open(self, destination):
        # opening stuff, and if successful:
        self._change_state(Open)

    def handle(self):
        raise AlreadyClosedError()

    def close(self):
        raise AlreadyClosedError()


class Open(Client):

    def open(self, destination):
        raise AlreadyOpenError()

    def handle(self):
        """ handling stuff """

    def close(self):
        # closing stuff, and if successful:
        self._change_state(Closed)

The Gang of Four book implements the state pattern differently. There, the stateful object holds a reference to a state, and all relevant calls get redirected to this state. But the authors also explain that this kind of stateful object "appears to change its class at runtime". In Python, thanks to its dynamism, we don't need to simulate a change of class at runtime, like we have to do in a less dynamic language, but instead do exactly that. With only two states it is probably overkill, but as you add more states and have more complex transition rules, it helps to keep methods short and simple. Every class represents a certain state the object can be in and if you get your transitions right, you can treat this as an invariant.

pillmuncher
  • 10,094
  • 2
  • 35
  • 33
  • +1 Interesting idea. I don't think I want to revamp my class hierarchy as I don't expect the number of states to grow much, and I need to have a number of classes which override the base class methods I currently have set up; but certainly worth remembering for the future. – tripleee Sep 25 '12 at 21:27