One of the lesser known techniques when designing APIs in Python is the concept of the fluent interface. Fluent interfaces help the user of your API to work with an object and it’s methods in a more concise manner and can therefore make your API simpler and more desirable to use.
Assume a basic Task
object, which might look something like this:
class Task(object):
def name(self, name):
self._name = name
return self
def cpu(self, cpu):
self._cpu = cpu
return self
def ram(self, ram):
self._ram = ram
return self
def dependencies(self, dependencies):
self._dependencies = dependencies
return self
@property
def command(self):
cmd = [self._name, '--cpu', self._cpu '--ram', self._ram, '--dep', self._dependencies]
return cmd
This object looks pretty common, basically a few setters and a command property. But have a look at all those setter functions – they all return self
– which is slightly less common.
By returning self
(the class instance) for every method on the object that modifies attributes, we enable method chaining and therefore implement a fluent interface.
This means that rather than setting attributes one line at a time, we can now configure the Task
in a single line of code and therefore increase the simplicity and readability of our code significantly.
So instead of the standard, non-fluent way of using the API,
# Usage of the above API without the fluent interface
task1 = Task()
task1.name('pre-execute')
task1.cpu(2)
task1.ram(8)
task1.dependencies([])
print(task1.command)
task2 = Task()
task2.name('main')
task2.cpu(8)
task2.ram(32)
task2.dependencies([task1._name])
print(task2.command)
task3 = Task()
task3.name('post-execute')
task3.cpu(2)
task3.ram(8)
task3.dependencies([task2._name])
print(task3.command)
we can now make our code far more concise and readable:
# Usage of the above API with the fluent interface
task1 = Task().name('pre-execute').cpu(2).ram(8).dependencies([])
print(task1.command)
task2 = Task().name('main').cpu(8).ram(32).dependencies([task1._name])
print(task2.command)
task3 = Task().name('post-execute').cpu(2).ram(8).dependencies([task2._name])
print(task3.command)