0

I'm fairly new at Python and can't seem to get this to work. I have a list which has an imbedded list of objects which are the names of functions and I need to execute the list of objects. I have functions that call lists of functions. I want to change this so I can call the list of functions directly without calling another function.

validActions=[
  ['Refresh','Environment Refresh',Refresh],
  ['Retire','Environment Retire Storage',
    [ doStatusDecommission,
      doPauseJobBeforeStart,
      doRetireStorage,
      doStatusDisconnected]],
  ['Provision','Environment Provision Storage',Provision]
]

def updateEnv(ctx):
  for actionVal,actionDesc,actionFunction in validActions:
    if ctx["newAction"] == actionVal:
      actionFunction()

This works if I'm calling "Refresh" or "Provision" as they are functions. However, this does not work when I call the list for "Retire" and the error message is

TypeError: 'list' object is not callable
5
  • Why can't you have an embedded list of references to the functions themselves? Commented Aug 13, 2013 at 21:26
  • You can make another function which calls all 4 of those functions. Commented Aug 13, 2013 at 21:28
  • @Asad: They already are; the title is inaccurate. Commented Aug 13, 2013 at 21:29
  • @IgnacioVazquez-Abrams I admit I just skimmed the body, but looking at it now I'm even more confused. Some of those are strings and some are references (presumably to functors). Is there a pattern? EDIT: I see it now. Commented Aug 13, 2013 at 21:32
  • 1
    @Asad: The third element. Commented Aug 13, 2013 at 21:32

5 Answers 5

3

You can still call each function in the list. If you make all entries lists then it becomes much easier to handle both cases:

validActions=[
    ['Refresh','Environment Refresh', [Refresh]],
    ['Retire','Environment Retire Storage', [
        doStatusDecommission,
        doPauseJobBeforeStart,
        doRetireStorage,
        doStatusDisconnected
    ]],
    ['Provision', 'Environment Provision Storage', [Provision]]
]

def updateEnv(ctx):
    for actionVal, actionDesc, actionFunctions in validActions:
        if ctx["newAction"] == actionVal:
            for action_function in actionFunctions:
                action_function()

If all you are doing is finding the one action that matches ctx['newAction'], you'd be better off using a dictionary and look up the actionDesc and actionFunctions items from that *directly:

validActions = {
    'Refresh': ('Environment Refresh', (Refresh,)),
    'Retire': ('Environment Retire Storage', (
        doStatusDecommission,
        doPauseJobBeforeStart,
        doRetireStorage,
        doStatusDisconnected
    ),
    'Provision': ('Environment Provision Storage', (Provision,)),
}

def updateEnv(ctx):
    actionDesc, actionFunctions = validActions[ctx["newAction"]]
    for action_function in actionFunctions:
         action_function()
Sign up to request clarification or add additional context in comments.

1 Comment

I think the OP is also wasting resources by using a list and looping through it to find a single "actionVal", determined by ctx["newAction"]. The OP should use a dictionary of lists of functors.
1

Make all of them lists and then iterate over the list executing each in turn.

for actionVal,actionDesc,actionFunctions in validActions:
  if ctx["newAction"] == actionVal:
    for actionFunction in actionFunctions:
      actionFunction()

Comments

1

One option is to have each list end with a list of functions, even if the list only has one element

validActions=[
  ['Refresh','Environment Refresh', [Refresh]], #note brackets around Refresh
  ['Retire','Environment Retire Storage',
    [ doStatusDecommission,
      doPauseJobBeforeStart,
      doRetireStorage,
      doStatusDisconnected]],
  ['Provision','Environment Provision Storage',[Provision]]
]

def updateEnv(ctx):
  for actionVal,actionDesc,actionFunctions in validActions:
    if ctx["newAction"] == actionVal:
      for func in actionFunctions:
        func()

Or make a new function which calls all 4 of those functions

def retireFunctions():
  doStatusDecommission()
  doPauseJobBeforeStart()
  doRetireStorage()
  doStatusDisconnected()

validActions=[
  ['Refresh','Environment Refresh',Refresh],
  ['Retire','Environment Retire Storage', retireFunctions],
  ['Provision','Environment Provision Storage',Provision]
]

Or a final option is a type test (not recommended)

def updateEnv(ctx):
  for actionVal,actionDesc,actionFunction in validActions:
    if ctx["newAction"] == actionVal:
      if callable(actionFunction):
        actionFunction()
      else:
        for func in actionFunction:
          func()

1 Comment

I already had the solution where I have one function call the other functions but I am moving away from that so I can keep track of all the functions being called in one place. Ending each list with a list of functions works for the implementation I need. Thanks.
0

I don't want to fill my answer with your defined list to make it look longer. I'll use the same list you defined.

def updateEnv(ctx):
    for actionVal,actionDesc,actionFunctions in validActions:
        if ctx["newAction"] == actionVal:
            try:
                [func() for func in actionFunctions]
            except TypeError:
                # not iterable
                actionFunctions()
            except Exception as e:
                # iterable, unexpected errors
                print e
                pass

If you can modify the data structure, I suggest the following:

validActions={
  ('Refresh','Environment Refresh') : {Refresh},
  ('Retire','Environment Retire Storage'):
    { doStatusDecommission,
      doPauseJobBeforeStart,
      doRetireStorage,
      doStatusDisconnected } ,
  ('Provision','Environment Provision Storage'):{Provision}
}

Your functions are generally method descriptor, which are immutable. Using set as container of your function can greatly reduce time cost in iterating and counting.

By using dictionary as top-level container with tuple keys, you can accurately map your input to output. What does that mean? It means:

def updateEnv(ctx):
    [[func() for func in value] if ctx["newAction"] == key[0] 
     else None
     for key, value in validActions.items()]

Even more efficiently, if you know actionDecs is unique to actionVal, use two separate mappings:

validActions = {
   'Refresh' : {Refresh},
   'Retire':
      { doStatusDecommission,
       doPauseJobBeforeStart,
       doRetireStorage,
       doStatusDisconnected } ,
   'Provision' : {Provision}
}

actionsDescs = {
    'Refresh': 'Environment Refresh'
    'Retire': 'Environment Retire Storage'
    'Provision' : 'Environment Provision Storage'
}

If you need to description from abbreviation, call for example:

actionsDescs['Refresh']

And you function iteration becomes:

def updateEnv(ctx):
    [func() for func in validAction[ctx["newAction"]]]

Feel free to comment if you have specific need or question on this.

Comments

-1

To get function by name

If functions defined in this module:

globals().get(actionFunctionName)(args)

If in other module or class:

getattr(class_or_module,actionFunctionName)(args)

3 Comments

The objects are already functions. They are not strings naming functions. The title is misleading.
Question was rewrited few times.
I adjusted the title after reading the question; I made that edit after I commented on your answer. It was the only edit made to the question.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.