A Quick Introduction to Python Interfaces
What is an interface object?
Python Interfaces are objects that denote, describe, and document the behavior of an object.
In the spirit of Python's dynamic nature, Interface objects also provide some useful mechanisms for you to use work with. For example, with Interfaces you can:
o Associate Interfaces with your objects
o Automatically imply an Interface from a class
o Extend one or more Interfaces with another Interface
o Ask an Interface if a class implements that Interface
o Ask an Interface if an instance implements that Interface
o Ask an Interface if it extends another Interface
o Generate plain-text documentation from an Interface
- o Generate HTML, DocBook and other formats from an Interface (needs
- Zope's StructuredTextNG package)
Interface objects describe the behavior of an object by containing useful information about the object. This information includes:
- o Prose documentation about the object. In Python terms, this
- is called the "doc string" of the interface. In this element, you describe how the object works in prose language and any other useful information about the object.
- o Descriptions of attributes. Attribute descriptions include
- the name of the attribute and prose documentation describing the attributes usage.
o Descriptions of methods. Method descriptions can include:
- o Prose "doc string" documentation about the method and its
- usage.
- o A description of the methods arguments; how many arguments
- are expected, optional arguments and their default values, the position or arguments in the signature, whether the method accepts arbitrary arguments and whether the method accepts arbitrary keyword arguments.
- o Optional tagged data. Interface objects (and their attributes and
- methods) can have optional, application specific tagged data associated with them. Examples uses for this are examples, security assertions, pre/post conditions, and other possible information you may want to associate with an Interface or its attributes.
Not all of this information is mandatory. For example, you may only want the methods of your interface to have prose documentation and not describe the arguments of the method in exact detail. Interface objects are flexible and let you give or take any of these components.
Why do you want to use Interfaces?
You want to use Interfaces because they solve a number of problems that arise while developing large systems with lots of developers.
- o Developers waste a lot of time looking at the source code of your
- system to figure out how objects work. This is even worse if someone else has already wasted their time doing the same thing.
- o Developers who are new to your system may misunderstand how your
- object works, causing, and possibly propagating, usage errors.
- o Because an object's interface is inferred from the source,
- developers may end up using methods and attributes that are meant for "internal use only".
- o Code inspection can be hard, and very discouraging to novice
- programmers trying to understand code written by gurus.
Interfaces try solve these problems by providing a way for you to describe how to use an object, and a mechanism for discovering that description.
Why don't I just write documentation?
That's a great idea! Interfaces are, in fact, a type of documentation. Not only are they a way to provide documentation, they are also a way to discover documentation in a standard, discoverable, presentation-neutral way that you can easily integrate into your own applications.Creating an Interface
Interfaces can be constructed from scratch, by calling the 'new' method to generate a new Interface object:
FooInterface = Interface.new('FooInterface')This creates a new, blank interface called 'FooInterface?'. 'new' expects one argument, the name of the interface to create. You can also pass 'new' a list of "base interfaces" that your new interface extends:
BarInterface = Interface.new('BarInterface') BazInterface = Interface.new('BazInterface', [FooInterface, BarInterface])In this example, 'BazInterface?' extends both 'FooInterface?' and 'BarInterface?'. You can examine the base interfaces of an interface just like you examine the base classes of a class, by looking at the interfaces 'getBases()' method:
>>> BazInterface.getBases() [<Interface FooInterface at 8107e28>, <Interface BarInterface at 80f8828>] >>>You can also ask an Interface if it extends another interface by calling the 'extends' method:
>>> BazInterface.extends(BarInterface) 1 >>>A third argument you can supply to 'new' when creating a new interface is a dictionary of 'attributes'. The keys of the dictionary are names that map to objects that describe the elements the interface defines. Currently, the Interface package provides two kinds of elements:
Attribute -- An attribute element can describe:
o A Name
o Prose documentation about the attribute
Method -- A Method element can describe:
o A Name
o Prose documentation about the attribute
o Information about the method's signature
Using these attributes, you can describe the interfaces for various objects:
attrs = {'foo', Interface.Method("The foo method"), 'bar', Interface.Method("The bar Method"), 'size', Interface.Attribute("The size attribute") } BazInterface = Interface.names('BazInterface', [FooInterface, BarInterface], attrs, )This example creates an interface, 'BazInterface?', that two interfaces, 'FooInterface?' and 'BarInterface?', and also contains three attributes: two methods and one attribute.
XXX programatic construction of method signatures XXX
Classes and Interfaces
You may notice a similarity between interfaces extending from other interfaces and classes sub-classing from other classes. This is a similar concept, but the two should not be considered equal. There is no assumption that classes and interfaces exist in a one to one relationship: a useful interface may be composed of several classes, and one class may implement several interfaces.
The distinction between a class and an interface should always be kept clear. The purpose of a class is to share the implementation of how an object works. The purpose of an interface is to document how to work with an object, not how the object is implemented. It is possible to have several different classes with very different implementations realize the same interface. Because of this, interfaces and classes should never be confused.
That being said, there is a "shortcut" syntax that can be used to build Python Interfaces that uses the Python 'class' sytax. Above, you saw how to build Interface objects from scratch by passing arguments to 'Interface.new'. If you were using a tool that built these interfaces for you, it would be easy. But if you were building complex interfaces, it would be dificult, and not very intuitive, to build these interfaces "by hand" in Python.
Keep in mind that this syntax can be a little misleading, because Interfaces are not classes. It is important to understand that using Python's class syntax is just a convenience, and that the resulting object is an interface, not a class.
To create an interface object using Python's class syntax, create a Python class that subclasses from 'Interface.Base':
class UserInterface(Interface.Base): """ This is the doc string for a User """ def getUserName(self): """ returns the user's name """ def getPassword(self): """ returns the user's password """ def getFavoriteColor(self): """ returns the user's favorite color """This class does not describe any kind of behavior for its methods, it just describes an interface that a typical "User" object would realize. By subclassing the 'Interface.Base' interface, the resulting object 'UserInterface' is an interface object.
Will create a new Interface object that is implied from the User class. Now, you can associate the UserInterface Interface with your new, concrete class that you define your user behavior in. For example:
class MyUser: __implements__ = UserInterface def __init__(self, name, passwd, color): self.name = name self.passwd = passwd self.color = color def getUserName(self): """ returns the user's name """ return self.name def getPassword(self): """ returns the user's password """ return self.passwd def getFavoriteColor(self): """ returns the user's favorite color """ return self.colorThis new class, MyUser defines a concrete class based on the User ABC. A class can realize more than one interface. For example, say you had an interface called ItemInterface? that described how an object worked as an item in a "Container" object. If you wanted to assert that MyUser instances realized the ItemInterface? interface as well as the UserInterface, you can provide a sequence of Interface objects to the MyUser class:
class MyUser: __implements__ = UserInterface, ItemInterfaceThis __implements__ sequence can also contain other sequences which contain interfaces, which in turn can contain sequences, and so on. So you could say, for example:
class MyUser: __implements__ = UserInterface, (ItemInterface, FooInterface)This is useful if you want to assert that one class implements an interface, and all of the interfaces that another class implements:
class MyFooUser: __implements__ = UserInterface, ItemInterface ... class MyBarUser: __implements__ = FooInterface, MyFooUser.__implements__This new class, MyBarUser, asserts that it implements the FooInterface?, as well as the interfaces implemented by the MyFooUser? class.
Querying an Interface
Interfaces can be queried for information about the interface they describe. The simplest case is to ask the Interface the names of all the various interface items it describes. From the Python interpreter, for example, you can walk right up to an Interface and ask it for its names:
>>> UserInterface.names() ['getUserName', 'getFavoriteColor', 'getPassword']Interfaces can also give you more interesting information about their items. Interface objects can return a list of '(name, description)' tuples about their items by calling the namesAndDescriptions method. For example:
>>> UserInterface.namesAndDescriptions() [('getUserName', <Interface.Method.Method instance at 80f38f0>), ('getFavoriteColor', <Interface.Method.Method instance at 80b24f0>), ('getPassword', <Interface.Method.Method instance at 80fded8>)]As you can see, the "description" of the Interface's three items in these cases are all Method objects. Descriptions objects can be either Attribute or Method objects. Both Attributes and Methods, and Interface objects themselves, implement the following interface:
'getName()' -- Returns the name of the object.
'getDoc()' -- Returns the documentation for the object.
Method objects provide a way to describe rich meta-data about Python methods. Method objects have the following interface:
'getSignatureInfo()' -- Returns a mapping of the following signature traits and values:
positional -- A sequence of positional arguments
required -- A sequence of required arguments
optional -- A mapping from optional arguments to their default values
varargs -- True if the method accepts arbitrary variable arguments
kwargs -- True if the method accepts arbitrary keyword arguments
- 'getSignatureString()' -- Returns a human-readable string representation
- of the method's signature.
Generating Documentation from Interfaces
You can use a handy utility function in the Interface package that prints simple "structured text" interface documentation for you. Here is some example output on the UserInterface class that shows this off:
>>> print Interface.interface_as_stx(UserInterface) UserInterface Prototype (scarecrow) Interfaces Implementation getUserName() -- returns the user's name getFavoriteColor() -- returns the user's favorite color getPassword() -- returns the user's passwordUsing Zope's "StructuredTextNG" package, you can turn this output into a Python DOM object, and then use the included rendering tools to turn this into HTML, DocBook (PDF and other formats are expected soon), or use other Python XML tools to render it into any format you want.
Lastly, you can ask an Interface if a certain class or instance that you hand it implements that interface. For example, say you wanted to know if the MyUser class implemented the UserInterface:
UserInterface.implementedBy(MyUser)would return true in this case. If you had an instance of MyUser, you can also ask the Interface if that instance implements the Interface:
UserInterface.implementedByInstancesOf(my_user_instance)This would also return true if my_user_instance was an instance of MyUser, or any other class that implemented the MyUser Interface.
Associating Application Data with Interfaces
For many applications additional information needs to be contained by Interface objects. For example, the Zope application has a fine-grained security system, where "Permissions" are associated with the methods of an object. A "user" does not have the sufficient security privileges to call a method if they do not have the right permission level that is associated with that method.
Many different applications may have different kinds of information they want to associate with Interfaces, Methods, and Attributes. Some possible kinds of information commonly associated with these objects are:
- pre/post-conditions -- You may want to specify a precondition (a set
- of conditions that must exist before using the object) or a postcondition (a set of conditions that exist after using the object)
- return type or description -- If your object is a method, you may
- want to associate a type of the return value or a description of the return value.
- security assertions -- For systems like Zope, or other multi-user
- environments, you may want to specify security assertions about who can use the object.
- Exceptions raised by method -- If your object is a method, you may
- want to specify possible exceptions the method can raise.
- Example usage -- Since interfaces are also documentation, you may
- want to associate an example that shows how the object is used.
The above kinds of information you may want to associate with an interface, attribute, or method are just examples. The Interface package does not specify any specific types of information that your application may be interested in, that is up to you to decide. Interface, Attribute, and Method objects implement to following interface for associating additional, application specific information with the object:
- 'setTaggedValue(tag, value)' -- Associates 'value' to the object with the tag
- 'tag'.
'getTaggedValue(tag)' -- Returns the value associated with 'key'.
- 'getTaggedValueTags()' -- Returns a list of all tags associated with this
- object.
Implementation Verification
The Interface package provides a utility function to verify that a class properly implements an Interface. The verification utility is passed both an interface and a class, and checks for the following conditions:
- o The class asserts that it implements the interface. If this test
- fails, the verification utility will raise a 'DoesNotImplement?' error.
- o The class implements all of the interfaces methods and attributes.
- If this test fails, the verification utility will raise a 'BrokenImplementation?' error.
- o The methods of the class have the correct signature as specified by
- the interface. If this test fails, the verification utility will raise a 'BrokenMethodImplementation?' error.
If the class properly implements the interface, the verification utility will raise no errors and return a true value. Here's an example of an interface called 'UserFolderInterface?' a class that implements that interface, and a test to verify that the class works properly:
import Interface class UserFolderInterface(Interface.Base): """ A User Folder """ def getUsers(self): """ Returns a list of users """ def addUser(self, username, password, real_name): """ Adds a user """ def delUser(self, username): """ Deletes a user """ class UserFolder: __implements__ = (UserFolderInterface,) __users = {} def getUsers(self): return self.__users.items() def addUser(self, username, password, real_name): self.__users[username] = User(username, password, real_name) def delUser(self, username): del self.__users[username] # If the class does not implement the interface properly, this will # raise an error: Interface.verify_class_implementation(UserFolderInterface, UserFolder):Optional Strict Interface Enforcement
Let's say you want to have strict interface enforcement, meaning you want to raise an error if a method or attribute is accessed on an object that is not defined in that objects Interface. Because Interface objects are passive, they make no attempt to enforce an interface on an object, so this is not currently possible.
There is a package, however, called mxProxy written by Marc-André Lemburg at "http://www.lemburg.com/files/python/mxProxy.html":http://www.lemburg.com/files/python/mxProxy.html that can be used to enforce a certain interface by wrapping your instances in a "proxy" object. Because mxProxy objects can be passes a simple list of names that define the objects interface, the 'Interface.names()' method could be used to provide this list.
Here is an example of a class that implements an interface and an mxProxy object that protects non-interface methods from being called:
import Interface import Proxy class IFoo(Interface.Base): def aMethod(self): """ a Method """ def bMethod(self): """ b Method """ class Foo: __implements__ = (IFoo,) def aMethod(self): return 'foo!' def bMethod(self): return 'bar!' def cMethod(self): return 'you should not be able to call this!' # first, create a 'factory' that accepts a class and a list of names # in the interface. FooFactory = Proxy.ProxyFactory(Foo, IFoo.names()) # now, instances of FooFactory will be protected: foo = FooFactory() foo.aMethod() # this will work foo.bMethod() # this will also work foo.cMethod() # this will raise a Proxy.AccessErrorFuture Proposals
This section of the documentation discusses some possible future courses that Python interfaces may take. An obvious goal would be to make Python interface objects first class Python objects. Here is an example of how something like that could spelled:
from OtherInterfaces import IBar, IBaz interface IFoo(IBar, IBaz): def aMethod(self): """ a Method """ def bMethod(self): """ b Method """This would create an Interface object that was equivalent to the following syntax under the current implementation:
import Interface from OtherInterfaces import IBar, IBaz class IFoo(Interface.Base, IBar, IBaz,) def aMethod(self): """ a Method """ def bMethod(self): """ b Method """
do these descriptions correspond to interfaces in zope3 alpha? --hernan, 2004/05/28 12:46 EST reply
I was looking for some docs about the python interfaces bundled in Zope3. Though this doc appears to correspond to the interface package in Zope3 alpha, there are things missing. For instance Interface.verify_class_implementation. While guessing that there were rationales for diverging implementation from the proposal, do you suggest any other readings?
i see... --hernan, 2004/05/28 12:53 EST reply
I missed the Interface frontpage. Sorry.