Use an Interface ================ To use interfaces, cast an object to the interface by wrapping the object with the interface (``Writable(object)``) To prevent interface checks from affecting performance, consider putting interface casts inside ``if __debug__:`` clauses. This performs interface checks during debugging, but production code can run faster using the original objects by running Python with the ``-O`` flag. .. code-block:: python def write_hello(writer): if __debug__: writer = Writer(writer) writer.write('Hello\n') output = OutputWriter() error = ErrorWriter() wrapped = PrintAttributeAccessWrapper(error) write_hello(output) write_hello(error) write_hello(wrapped) Not only is the object checked that it supports the ``write`` attribute. Uses of the interface are checked that they do not use non-supported attributes. .. code-block:: python def broken_write_hello(writer): if __debug__: writer = Writable(writer) writer.write('hello') writer.flush() broken_write_hello(OutputWriter()) In the above code, ``writer`` will be replaced by the interface, and the attempt to use ``flush``, which is not part of the interface, will fail, even though the passed object does support that attribute. In optimised Python, ``broken_hello_world`` will use the original object, and should run faster without the intervening interface replacement. In this case, the code will work with the current implementation, but may fail if a different object, that does not support ``flush``, is passed. Hopefully, by using ``jute`` this bug was caught during development. Interfaces can also be returned from a function. This is useful to ensure that callers are only using the "public" attributes of the returned object. This makes it easier to modify the implementation to return a different object. As long as the returned object satisfies the interface, all code should continue to work. .. code-block:: python def get_output(): output = sys.stdout if __debug__: output = Writable(output) return output This definition of ``get_output`` can be changed to use a non-flushable object (e.g. an ``ssl.SSLSocket``) with no risk that code that uses the returned value relies on non-supported attributes such as ``flush``. Returning interfaces can also be used to divide a complex object's method into those needed for specific roles, and only pass the appropriate subset to code implementing the roles. For example, an object which signals a condition can be divided into a Notifiable interface and a Watchable interface, to prevent a watching object from accidentally notifying completion. .. code-block:: python class Notifiable(jute.Opaque): def notify(self): """Notify that an event occurred.""" class Watchable(jute.Opaque): def watch(self, callback): """Get called when an event occurs.""" @jute.implements(Notifiable, Watchable) class Signal: def __init__(self): self.result = None self.callbacks = [] def notify(self, result): self.result = result for f in self.callbacks: f(result) self.callbacks = [] def watch(self, callback): if self.result: callback(result) else: self.callbacks.append(callback) def do_task(): signal = Signal() do_async(subtask, Notifiable(signal)) return Watchable(signal) task = do_task() task.watch(func) # OK task.notify(3) # Error