Thom Smith
Thom Smith

Reputation: 14086

Name a positional parameter "from"

I'm writing a script to translate ColdFusion CFML code to CFScript code. In many places, it will take a dictionary of attributes, look through a list of functions, and invoke the first one whose parameters match the given attributes with the dictionary as keyword args:

import inspect

def invokeFirst(attributes, *handlers):
    given_args = set(attributes)

    for handler in handlers:
        meta = inspect.getargspec(handler)

        allowed_args  = set(meta.args)
        required_args = set(meta.args[:-len(meta.defaults)]) if meta.defaults else meta.args

        if required_args <= given_args and (meta.keywords or given_args <= allowed_args):
            return handler(**attributes)

    raise TypeError("Can't invoke with arguments {}.".format(str(given_args)))

Example of usage:

def replaceLoop(tag):
    forIn = 'for (var {item} in {collection}) {{'

    return invokeFirst(tag.attributes,
        lambda item, collection: forIn.format( item=bare(item) , collection=collection ),
        lambda index, array    : forIn.format( item=bare(index), collection=index ),

        lambda _from, to, index:
            'for (var {index} = {min}; {index} <= {max}; {index}++) {{'.format(
                index=bare(index), min=_from, max=to,
            ),
    )

Now, because from is not a valid parameter name, I have to prefix it in the lambda and add a bunch of extra logic to invokeFirst (not shown). Is there any easier workaround that doesn't bloat the syntax at the point of use?

Upvotes: 1

Views: 26

Answers (1)

Gerrat
Gerrat

Reputation: 29730

This is likely too simplistic for your use case (or you might even consider this "bloating the syntax at the point of use"); but could you rely on Python's EAFP principle: try calling the functions, and just ignoring any exceptions? Something like:

def invokeFirst(attributes, *handlers):

    for handler in handlers:
        try:
            val = handler(attributes)
            return val
        except (KeyError, TypeError):
            pass

    raise TypeError("Can't invoke with arguments {}.".format(str(attributes)))


def replaceLoop(tag):
    forIn = 'for (var {item} in {collection}) {{'

    return invokeFirst(tag.attributes, 
        lambda d: forIn.format( item=bare(d['item']) , collection=d['collection'] ),
        lambda d: forIn.format( item=bare(d['index']), collection=d['index'] ),
        lambda d: 
            'for (var {index} = {min}; {index} <= {max}; {index}++) {{'.format(
                index=bare(d['index']), min=d['from'], max=d['to'],
            )

    )

bare = lambda b: b    

class Tag:
    def __init__(self, dct):
        self.attributes = dct

tag = Tag({'item':4})
print(replaceLoop(tag))

#satisfies 2nd function - outputs:
for (var 4 in 4) {

Upvotes: 1

Related Questions