[Python-3000] PEP Parade
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.
* A DocInfo object is a DocInfo-list of strings, and more DocInfo objects
in fifo order, with a tag attribute and a depth first iterator method.
Ultimately the contents are strings (or string like objects) that came from
some input source.
(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