Reputation: 14086
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
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