Unable to call a function by using a string

2.2k views Asked by At

I am trying to call a function in a class based on the string I passed in.

I tried following the steps at this link: Calling a function of a module from a string with the function's name in Python

Here is my code:

methodNameString = "add" + typeOfShip
methodToCall = getattr(listOfPlayerFleets[currentPlayer], methodNameString)

listOfPlayerFleets[currentPlayer].methodToCall(num)

I get the error:

AttributeError:fleet instance has no attribute 'methodToCall'

Any ideas as to why methodToCall isn't being assigned my correct method name?

I also tried

methodToCall = getattr(fleet, methodToCall)

then I get the message:

AttributeError: 'module' object has no attribute 'addCruiser'

Its as if the getattr can't find my methods in my class.

the listOfPlayerFleets is a list of fleet objects

Here is what the fleet object looks like you can see the methods do really exist.

class fleet:
    """ Stores Fleet Numbers, Represents a fleet """

    ships = {'fighters':0, 'cruisers':0, 'capitols':0}
    attacking = False
    defending = False

    def __init__(self):
        self.ships = {'fighters':0, 'cruisers':0, 'capitols':0}
        self.attacking = False
        self.defending = False

    #add a Fighter
    def addFighter(self, numOfFighters):
        self.ships['fighters'] = numOfFighters


    #add a Cruiser
    def addCruiser(self, numOfCruisers):
        self.ships['cruisers'] = numOfCruisers

    #add a Capitol Ship
    def addCapitol(self, numOfCapitols):
        self.ships['capitols'] = numOfCapitols
4

There are 4 answers

2
robert On BEST ANSWER

Your methodToCall variable is a bound method, meaning you don't need to call it on an object--it knows the object it will be called on. fleet.addFighter, for example, is an unbound method. Printing repr(methodToCall) and repr(fleet.addFighter) should make this clear.

You should use this:

methodNameString = "add" + typeOfShip
methodToCall = getattr(listOfPlayerFleets[currentPlayer], methodNameString)

methodToCall(num)
2
Reiner Gerecke On

That should do it.

methodNameString = "add" + typeOfShip
methodToCall = getattr(listOfPlayerFleets[currentPlayer], methodNameString)

methodToCall(num)

The getattr gives you a reference to the method of that specific instance (listOfPlayerFleets[currentPlayer]), so just call it with the parameters.

1
AudioBubble On

First off, this kind of thing is rarely on par with a proper solution, e.g. using dictionary, and even less rarely superior to those. You sould propably have a method addShip(kind, num) that just does self.ships[kind] += num. Much cleaner, easier to extend, DRY (don't repeat yourself) and as an extra bonus also faster.

As for the errors: listOfPlayerFleets[currentPlayer].methodToCall(num) tries to call the method called methodToCall (which obviously doesn't exist. getattr(listOfPlayerFleets[currentPlayer], methodNameString) already got you the method you want, and it's a bound method, i.e. when you call methodToCall(), the right self is passed.

The other error ('method' object has no ...) is because there's a difference between modules and the things (e.g. classes) contained in it. I suppose the class fleet is in a module called fleet? Then you need fleet.fleet. By the way, classes should be named in CamelCase - see the style guide, PEP 8.

0
Philippe Lavoie On

Did you try

func = getattr(obj, "method")
if callable(func):
  result = func(args)