Wednesday, June 24, 2015

Learning about callable types

Another technique I used in the HTML translator is the Python equivalent of a "computed go-to". The Translator API passes a series of "events" and any translator has to deal with them something like this:

    for (code, text, stuff, lnum) in event_generator :
        # deal with this event

There are 34 possible event codes. A naive way of "dealing with this event" would be to write an if..elif..elif stack 34 items high. If you put the most frequent codes at the top this would not be too bad in performance, but it makes for rather unwieldy code to edit. A better way is to to have a dict in which the keys are the 34 code values, and the values are the actions to be performed for a given key. I've provided a skeleton of a Translator module with code like this:

    actions = {
        XU.Events.LINE          : None,
        XU.Events.OPEN_PARA     : None,
        XU.Events.CLOSE_PARA    : None,
...
        XU.Events.PAGE_BREAK    : note_page_break ,
...
        XU.Events.CLOSE_TABLE   : "</table>" ,
        }

    for (code, text, stuff, lnum) in event_generator :
        action = actions[ code ]
        if action : # is not None or null string,
            if isinstance( action, str ) :
                BODY << action # write string literal
            else :
                action() # call the callable
        # else do nothing

In this version, items in the dict can be one of three things:

  • None or a null string, meaning either "do nothing" or "not implemented yet"
  • A string literal to be copied to the output file immediately
  • A reference to a callable which will do something more involved, perhaps formatting the text value in some way before writing it.

(The callable functions named in this action dict were the "flock of little helper functions" that I referred to in yesterday's post—the ones that needed access to variables initialized by their parent function, and which had to become globals due to Python's eccentric scoping rules.)

For one part of the HTML Translator, I had a similar action dict but in it I wanted to have four possible types of actions:

  • None, meaning "do nothing"
  • A string literal to be copied to the output file immediately
  • A reference to a callable that would do something more involved such as setting or clearing a status flag.
  • A lambda that yielded a string value based on the text value but didn't actually contain a file-output call.

No problem, thought I. The above loop logic can easily be stretched to deal with this along these lines:

   for (code, text, stuff, lnum) in event_generator :
        action = actions[ code ]
        if action : # is not None or null string,
            if isinstance( action, str ) :
                BODY << action # write string literal
            elif type(action) == types.LambdaType :
                BODY << action() # invoke lambda, write its value
            else : # type(action) == FunctionType
                action() # call the callable
        # else do nothing

Surprise! It didn't work. Why? Turns out, although the types module has distinct names FunctionType and LambdaType, they are equal. You cannot distinguish between a reference to a lambda and a reference to a function based on type().

That kinda makes sense, in that we are told repeatedly that a lambda is just shorthand for an anonymous function. But it would have been handy to tell the difference.

In the end, for this part of the code (it was not the main translate loop but one with fewer possible codes) I made each value of the action dict a tuple ('f', funct_name) or ('s','literal string'). That allowed me to distinguish between a lambda that generated a string, and a function that didn't. But the whole thing felt like a kludge and I believe I will go back and recode that particular loop as an if/elif stack instead.

No comments: