Reputation: 529
Lets say we want to implement a functionality using a class
class MyClass():
def __init__(self):
pass
def generate_query(self):
# Generate the query
pass
def send_query(self):
# send the query over the network
pass
def receive_response(self):
# read the response from buffer
pass
What's the best way to make sure that generate_query()
has been called before send_query()
, ofcourse vice versa doesn't make sense. It's important because simply mentioning in the API documentation to call another method before you've called send_query()
is another thing, but checking it explicitly in the code in send_query()
that generate_query()
has been called before is a good practice IMO.
I am expecting a solution like if generate_query()
has not been called, we raise an exception or so.
There was a nice pythonic way to do this, I had read somewhere, but I forgot the source and the solution both.
Help is appreciated!
Upvotes: 1
Views: 1247
Reputation: 811
I've come up with a base class you can inherit from to enforce method call order (tested in Python 2.7 and 3.6):
from types import FunctionType, MethodType
class Sequenced(object):
def __init__(self):
_methods = [name for name, obj in self.__class__.__dict__.items()
if type(obj) == FunctionType]
assert set(self._method_sequence).issubset(set(_methods))
self._sequence_pos = 0
def __getattribute__(self, name):
attr = object.__getattribute__(self, name)
if type(attr) == MethodType:
if attr.__name__ in self._method_sequence:
if self._sequence_pos >= len(self._method_sequence):
raise RuntimeError("All sequenced methods already called.")
if attr.__name__ != self._method_sequence[self._sequence_pos]:
raise RuntimeError("{0} method call expected.".format(
self._method_sequence[self._sequence_pos]))
self._sequence_pos += 1
def func_wrapper(*args, **kwargs):
return attr(*args, **kwargs)
return func_wrapper
else:
return attr
Be warned, I don't fully understand how this works (I've managed to stop __getattribute__
from causing infinite recursion, but I don't understand what caused it in the first place, and I don't understand why I have to use FunctionType
in one place and MethodType
in another). It passed my minimal testing (Python 2.7 and 3.6), but you'll want to make sure you test it as well.
All you need to do with your class is make it inherit from Sequenced
, and modify its __init__
method like so:
class MyClass(Sequenced):
def __init__(self):
self._method_sequence = [
"generate_query",
"send_query",
"receive_response"
]
super(MyClass, self).__init__()
def generate_query(self):
# Generate the query
pass
def send_query(self):
# send the query over the network
pass
def receive_response(self):
# read the response from buffer
pass
The base class allows your class to contain other methods (sequencing is only enforced on methods within the _method_sequence
list). _method_sequence
can contain duplicates if you want methods to be called more than once.
In the case of a sequenced method being called out of sequence, or attempting to use a sequenced method after already having gone through the list, RuntimeError
is raised.
It's possible to modify the base class so that sequence methods can be called repeatedly once available, but I'll leave that as an exercise to the reader.
Upvotes: 1
Reputation: 14863
One option could be to use boolean flags. This approach is simple, but not very clean.
class MyClass():
def __init__(self):
self.generated = False
def generate_query(self):
self.generated = True
# Generate query
def send_query(self):
if not self.generated:
self.generate_query()
# Send query
def receive_response(self):
pass
I would advice you to try to reconstruct the class and methods. Requiring methods to run in a particular order is prone for errors, especially if it is not documented.
Upvotes: 0
Reputation: 1363
My idea would be to keep a instance variable name flag which will be True,if the generate_query is called, and if flag is false, it means that generate_query is not called and so, you will call the generate_query inside send_query or show a message.
Upvotes: 0