Zope is becoming a component system. Zope components will be Python objects with interfaces that describe them. Right now only some of the Zope code base uses interfaces. In coming releases more and more of Zope will include interfaces. As a Zope developer you can use interfaces right now to build your Zope components.
Components are objects that are associated with interfaces. An interface is a Python object that describes how you work with other Python objects. In this chapter, you’ll see some simple examples of creating components, and a description of interfaces and how they work.
Here is a very simple component that says hello. Like all components, this one generally consists of two pieces, an interface, and an implementation:
from Interface import Base
class Hello(Base):
"""The Hello interface provides greetings."""
def hello(self, name):
""" Say hello to the name """
class HelloComponent:
__implements__ = Hello
def hello(self, name):
return "hello %s!" % name
Let’s take a look at this step by step. Here, you see two Python class statements. The first statement creates the interface, and the second statement creates the implementation.
The first class statement creates the ‘Hello’ interface. This interface describes one method, called ‘hello’. Notice that there is no implementation for this method, interfaces do not define behavior, they just describe a specification.
The second ‘class’ statement creates the ‘HelloComponent’ class. This class is the actual component that does what ‘Hello’ describes. This is usually referred to as the implementation of ‘Hello’. In order for you to know what interfaces ‘HelloComponent’ implements, it must somehow associate itself with an interface. The ‘__implements__’ class attribute does just that. It says, “I implement these interfaces”. In this case, ‘HelloComponent’ asserts that it implements one interface, ‘Hello’.
The interface describes how you would work with the object, but it doesn’t dictate how that description is implemented. For example, here’s a more complex implementation of the ‘Hello’ interface:
import xmlrpclib
class XMLRPCHello:
__implements__ = Hello
def hello(self, name):
"""
Delegates the hello call to a remote object using XML-RPC.
"""
s = xmlrpclib.Server('http://www.zope.org/')
return s.hello(name)
This component contacts a remote server and gets its hello greeting from a remote component.
And that’s all there is to components, really. The rest of this chapter describes interfaces and how you can work with them from the perspective of components. In Chapter 3, we’ll put all this together into a Zope product.
Interface describe the behavior of an object by containing useful information about the object. This information includes:
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.
Interfaces solve a number of problems that arise while developing large systems with lots of developers.
Interfaces try to solve these problems by providing a way for you to describe how to use an object, and a mechanism for discovering that description.
The first step to creating a component, as you’ve been shown, is to create an interface.
Interface objects can be conveniently constructed using the Python ‘class’ statement. 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’:
from Interface import Base
class Hello(Base):
def hello(self, name):
"""Say hello to the world"""
This interface does not implement behavior for its methods, it just describes an interface that a typical “Hello” object would realize. By subclassing the ‘Interface.Base’ interface, the resulting object ‘Hello’ is an interface object. The Python interpreter confirms this:
>>> Hello
<Interface Hello at 812cbd4>
Now, you can associate the ‘Hello’ Interface with your new, concrete class in which you define your user behavior. For example:
class HelloComponent:
__implements__ = Hello
def hello(self, name):
return "Hello %s!" % name
This new class, ‘HelloComponent’ is a concrete class that implements the ‘Hello’ interface. A class can realize more than one interface. For example, say you had an interface called ‘Item’ that described how an object worked as an item in a “Container” object. If you wanted to assert that ‘HelloComponent’ instances realized the ‘Item’ interface as well as ‘Hello’, you can provide a sequence of Interface objects to the ‘HelloComponent’ class:
class HelloComponent:
__implements__ = Hello, Item
This ‘__implements__’ attribute is called an interface assertion. An interface assertion can be either an interface, or a sequence of interface assertions. Here’s a more complex example:
class Sandwich:
__implements__ = (Food, (Nourishing, Delicious), (GetsStaleQuickly,
(EdibleWithHands, GoodForLunch)))
Interface assertions allow complex nesting of interfaces. This is mostly useful when you wish to assert that your class implements some specific interfaces, along with whatever interfaces your base class implements:
class Sandwich(Food):
__implements__ = (EdibleWithHands, GoodForLunch, Food.__implements__)
Take care before you assert that your class implements the interfaces of your base classes.
Interfaces can extend other interfaces. For example, let’s extend the ‘Hello’ interface by adding an additional method:
class SmartHello(Hello):
"""A Hello object that remembers who it's greeted"""
def lastGreeted(self):
"""Returns the name of the last person greeted."""
‘SmartHello’ extends the ‘Hello’ interface. It does this by using the same syntax a class would use to subclass another class.
Now, you can ask the ‘SmartHello’ for a list of the interfaces it extends with ‘getBases’:
>>> SmartHello.getBases()
[<interface Hello at 80c72c8>]
An interface can extend any number of other interfaces, and ‘getBases’ will return that list of interfaces for you. If you want to know if ‘SmartHello’ extends any other interface, you could call ‘getBases’ and search through the list, but a convenience method called ‘extends’ is provided that returns true or false for this purpose:
>>> SmartHello.extends(Hello)
1
>>> SmartHello.extends(Sandwich)
0
>>>
Here you can see ‘extends’ can be used to determine if one interface extends another.
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; one class may implement several interfaces, and a class may not implement its base classes’s 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.
Interfaces can be queried for information. The simplest case is to ask an 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:
>>> User.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:
>>> User.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. Description objects can be either ‘Attribute’ or ‘Method’ objects. Attributes, methods, and interface objects implement the following interface:
- 'getName()' -- Returns the name of the object.
Method objects provide a way to describe rich meta-data about Python methods. Method objects have the following methods:
For example:
>>> m=User.namesAndDescriptions()[0][1]
>>> m
<Interface.Method.Method instance at 80f38f0>
>>> m.getSignatureString()
'(fullName=1)'
>>> m.getSignatureInfo()
{'varargs': None, 'kwargs': None, 'optional': {'fullName': 1},
'required': (), 'positional': ('fullName',)}
You can use ‘getSignatureInfo’ to find out the names and types of the method parameters.
You can ask an interface if a certain class or instance that you hand it implements that interface. For example, say you want to know if instances of the ‘HelloComponent’ class implement ‘Hello’:
Hello.implementedByInstancesOf(HelloComponent)
This is a true expression. If you had an instance of ‘HelloComponent’, you can also ask the interface if that instance implements the interface:
Hello.implementedBy(my_hello_instance)
This would also return true if my_hello_instance was an instance of HelloComponent, or any other class that implemented the Hello Interface.
Interfaces provide a simple way to describe your Python objects. By using interfaces you document your objects’ capabilities. As Zope becomes more component oriented, your objects will fit right in. While components and interfaces are forward looking technologies, they are useful today for documentation and verification.