[Python-3000] PEP Parade (original) (raw)

Ron Adam rrr at ronadam.com
Wed May 2 15:24:58 CEST 2007


Phillip J. Eby wrote:

At 07:37 PM 5/1/2007 -0700, Guido van Rossum wrote:

That's one solution. Another solution would be to use GFs in Pydoc to make it overloadable; I'd say pydoc could use a bit of an overhault at this point. True enough; until you mentioned that, I'd forgotten that a week or two ago I got an email from somebody working on the pydoc overhaul who mentioned that he had had to work up an ad-hoc generic function implementation for just that reason. :)

Ah, That would be me. :-)

I'm still working on it and hope to have it done before the library reorganization. (Minus Python 3000 enhancements since it needs to work with python 2.6)

The resulting (yes add-hoc) solution, is basically what I needed to do to get it to work nicely.

Talon showed me an example of his that used a decorator to initialize a dispatch table. Which is much easier to maintain over manually editing it as I was doing.

Here is an outline of how it generally works. Maybe you can see where proper generic functions might be useful.

INTROSPECTION or (Making a DocInfo Object.)

The actual introspection consists of mostly using the inspect module or looking directly at either the attributes, files, or file system. The end product is a data structure containing tagged strings that can be parsed and formatted at a later stage.

(Note: All or any of this can be used outside of pydoc if it is found to be generally useful.)

General use:

 1.  Create a inspection dispatcher.

           select = Dispatcher()   # A dispatcher/dictionary.

 2.  Define introspective functions, and use a decorator to
     add them to the dispatcher.

           @select.add('tag')
           foo(tag, name, obj):
              # The tag is added by the dispatcher.
              items = get_some_info_about_obj()
              title = DocInfo('title', name)
              body = DocInfo('list', items)
              return DocInfo(tag, title, body)

      Do the above for all unique objectes.
     (Functions can have have more than one tag name.)

 3.  Get input from the help function, interactive help, or web
     server request and create a DocInfo structure.

    get_info(request):

            # parse if needed. (search, topics, inexes, etc...)

            obj = locate(request)
            tag = describe(obj)   # get a descripton that matches a tag.
            return select(tag, request, obj)

Some keys are very general such as 'list', 'item', 'name', 'text', and some are specific to the source the data came from... 'package', 'class', 'module', 'function', etc...

If all you want is to send the text out the way it came in... you can use simple string functions.

result = DocInfo(request).format(str)

That will produce one long everythingruntogether output.

def repr_line(s): return repr(s) + '\n'

result = DocInfo(request).present(repr_line)

This will put each tagged string on it's own line with quotes around it. Since it's a nested structure the quotes will be nested too.

ADVANCED FORMATTING

Create a DocFormatter object to format a DocInfo data structure with.

 1.  Define a pre_processor function. (optional)

     This alters the data structure.  For example you can
     rearrange, remove, and/or replace parts before any formatting
     has occured.

 2.  Define a pre_formatter function.  (optional)

     Pre-format input strings at the bottom (depth first) but not
     intermediate result strings.  Any function that takes a string
     and returns a string is fine here.  ie... cgi.escape()

 3.  Define a formatter to format DocInfo list objects according to
     the tags.  (list objects are joined after sub items are formatted.)

* A function with an if-elif-else structure here is
     perfectly fine, but a dispatcher is better more complex things. ;-)

     (a)  Create a dispatcher object.

     (b)  Add functions to the dispatcher by using decorators and
          tag names.

     * The tags passed to the functions by the dispatcher contains
     all parent tags prepended to them with '.' seperators.  This
     allows you to format based on where something is in addition
     to what it is.

The dispatcher class is the formatter function in this case. The call method is used to keep it interchangeable with regular functions. So a method @dispatcher.add(tag1) is used as the decorator to add functions to the dispatcher.

 select = Dispatcher()

 @select.add('function', 'method')
 def format_function(tag, info):
    ...
    return formatted_info

Multiple tags allow a single function to perform several roles.

 4.  Create a post_formatter.  (optional)

     An example of this might be to replace place holders with
     dynamic content collected during the formatting process.

 5.  Combine the formatters into a single DocFormatter class.

     Example from the html formatter:

         formatter = DocFormatter(preformat=cgi.escape,
                                  formatter=select,
                                  postformat=page)

The callable, 'select', is the dispatcher, and 'page' is a post-formatter that wraps the contents into the final html page with head, navigation bar and the body sections.

The DocInfo format method iterates it's contents and calls the formatter on it. The formatter determines what action needs to be taken by what's given it and whether or not it's the first time or last time it is called.

And so to put it all together...

    result = DocInfo(request).format(formatter)

I currently have three formatters.

- text/console
- html
- xml

It should be very easy to write other formatters by using these as starting points.

Cheers, Ron



More information about the Python-3000 mailing list