[Python-3000] Some canonical use-cases for ABCs/Interfaces/Generics

Talin talin at acm.org
Wed May 2 05:10:53 CEST 2007


One of my concerns in the ABC/interface discussion so far is that a lot 
of the use cases presented are "toy" examples. This makes perfect sense 
considering that you don't want to have to spend several pages 
explaining the use case. But at the same time, it means that we might be 
solving problems that aren't real, while ignoring problems that are.

What I'd like to do is collect a set of "real-world" use cases and 
document them. The idea would be that we could refer to these use cases 
during the discussion, using a common terminology and shorthand examples.

I'll present one very broad use case here, and I'd be interested if 
people have ideas for other use cases. The goal is to define a small 
number of broadly-defined cases that provide a broad coverage of the 
problem space.

====

The use case I will describe is what I will call "Object Graph 
Transformation". The general pattern is that you have a collection of 
objects organized in a graph which you wish to transform. The objects in 
the graph may be standard Python built-in types (lists, tuples, dicts, 
numbers), or they may be specialized application-specific types.

The Python "pickle" operation is an example of this type of 
transformation: Converting a graph of objects into a flat stream format 
that can later be reconstituted back into a graph.

Other kinds of transformations would include:

   Serialization: pickling, marshaling, conversion to XML or JSON, ORMs 
and other persistence frameworks, migration of objects between runtime 
environments or languages, etc.

   Presentation: Conversion of a graph of objects to a visible form, 
such as a web page.

   Interactive Editing: The graph is converted to a user editable form, 
a la JavaBeans. An example is an user-interface editor application which 
allows widgets to be edited via a property sheet. The object graph is 
displayed in a live "preview" window, while a "tree view" of object 
properties is shown in a side panel. The transformation occurs when the 
objects in the graph are transformed into a hierarchy of key/value 
properties that are displayed in the tree view window.

These various cases may seem different but they all have a similar 
structure in terms of the roles of the participants involved. For a 
given transformation, there are 4 roles involved:

   1) The author of the objects to be transformed.
   2) The author of the generic transform function, such as "serialize".
   3) The author of the special transform function for each specific class.
   4) The person invoking the transform operation within the application.

We can give names to these various bits of code if we wish, such as the 
"Operand", the "General Operator", the "Special Operator", and the 
"Invocation". But for now, I'll simply refer to them by number.

Using the terminology of generic functions, (1) is the author of the 
argument that is passed to the generic function, (2) is the author of 
the original "generic" function, (3) is the author of the overloads of 
the generic function, and (4) is the person calling the generic function.

Each of these authors may have participated at different times and may 
be unaware of each other's work. The only dependencies is that (3) must 
know about (1) and (2), and (4) must know about (2).

Note that if any of these participants *do* have prior knowledge of the 
others, then the need for a generic adaption framework is considerably 
weakened. So for example, if (2) already knows all of the types of 
objects that are going to be operated on, then it can simply hard-code 
that knowledge into its own implementation. Similarly, if (1) knows that 
it is going to be operated on in this way, then it can simply add a 
method to do that operation. Its only when the system needs to be N-way 
extensible, where N is the number of participants, that a more general 
dispatch solution is required.

A real-world example of this use case is the TurboGears/TurboJSON 
conversion of Python objects into JSON format, which currently uses 
RuleDispatch to do the heavy lifting.

    @jsonify.when(Person)
    def jsonify_person(obj):
       # Code to convert a Person object to a dict
       # of properties which can be serialized as JSON

In this example, the "Person" need never know anything about JSON 
formatting, and conversely the JSON serialization framework need know 
nothing about Person objects. Instead, this little adaptor function is 
the glue that ties them together.

This also means that built-in types can be serialized under the new 
system without having to modify them. Otherwise, you would either have 
to build into the serializer special-case knowledge of these types, or 
you would have to restrict your object graph to using only special 
application-specific container and value types. Thus, a list of Person 
objects can be a plain list, but can still be serialized using the same 
persistence framework as is used for the Person object.

===

OK that is the description of the use case. I'd be interested to know 
what uses cases people have that fall *outside* of the above.

-- Talin


More information about the Python-3000 mailing list