-3

I have a python3 object (class instance), generated by an

myObj = myClass()

line. I have also a myMethodName string:

myMethodName = "exampleName"

. How can I call a method of myObj named by myMethodName? Thus, in this case, I want to call myObj.exampleName, which is coming from like a getAttr(myObj, myMethodName).

Unfortunately, a naive solution refered by the python docs (for example, here) gives my only a KeyError or AttributeError.

This fails on that myObj doesn't have a method method:

method = getAttr(myObj, myMethodName)
myObj.method(param)

This fails on that myMethodName has more of fewer arguments:

method = getAttr(myObj, myMethodName)
method(myObj, param)

Calling simply

method = getAttr(myObj, myMethodName)
method(param)

which would be the most logical, gives

TypeError: exampleName() takes 1 positional argument but 2 were given

So, how can I do this?

I use python 3.6, if it matters.


Extension: here is an MCVE:

class Test:
  name = None
  def __init__(name):
    self.name = name
  def process():
    method = getattr(self, 'process_' + name)
    method("param")
  def process_test(param):
    print ("process_test(): "+param)

test = Test("cica")
test.process("test")
peterh
  • 11,875
  • 18
  • 85
  • 108
  • 2
    Your `method` already is the method, can't you just call it like `method(params)`? I'd test it myself if you had provided an MCVE... – Stefan Pochmann Feb 03 '17 at 11:10
  • @StefanPochmann Also this gives an error. I extended it into the question. – peterh Feb 03 '17 at 11:13
  • @StefanPochmann MCVE added. – peterh Feb 03 '17 at 11:17
  • That already fails at the **initializer** with `TypeError: __init__() takes 1 positional argument but 2 were given`. Actual working MCVE, please. – Stefan Pochmann Feb 03 '17 at 11:19
  • @StefanPochmann Exactly this actually working MCVE what I couldn't produce. The cause of the problem was that I didn't give `self` to the first argument of `process_test()`... It should have been `def process_test(self, param)`. Thanks! – peterh Feb 03 '17 at 11:36

3 Answers3

2

If the attribute passed to getattr() is a method name then it returns a callable which is already bound to the object:

getattr(object, name[, default])

Return the value of the named attribute of object. name must be a string. If the string is the name of one of the object’s attributes, the result is the value of that attribute. For example, getattr(x, 'foobar') is equivalent to x.foobar. If the named attribute does not exist, default is returned if provided, otherwise AttributeError is raised.

Proof:

>>> x = [1, 2, 1, 3, 1, 1]
>>> f = getattr(x, 'count') # same as f = x.count
>>> f(1)
4
Leon
  • 31,443
  • 4
  • 72
  • 97
  • The result: `TypeError: exampleName() takes 1 positional argument but 2 were given`. Note: It is not a builtin class, it is a class defined by me. – peterh Feb 03 '17 at 11:22
  • @peterh Your problem is that you don't specify the `self` formal argument in your method signatures. [Jacques Gaudin's answer](http://stackoverflow.com/users/1388292/jacques-gaudin) fixes that problem. – Leon Feb 03 '17 at 11:32
2

Your example can be changed for the following:

class Test:
   def __init__(self, name):
       self.name = name
   def process(self, param):
       method = getattr(self, 'process_' + param)
       method("param")
   def process_test(self, param):
       print ("process_test(): "+param)


test = Test("cica")
test.process("test")

I think you are missing the concept of bound/static method in Python. See this answer, it is describing the different types of methods.

Community
  • 1
  • 1
Jacques Gaudin
  • 15,779
  • 10
  • 54
  • 75
1

The first error is caused, because getattr returns a method that is already bound to the object instance (myObj).

So all you have to do is call your method variable as if it was a function.

The second error is probably due to your exampleName not having the same number of parameters as the number of arguments passed to method.

All you have to do for the second error is to add the same parameters to the method in the class:

myObj = myClass()class myClass:
    def exampleName(self, *args, **keyargs):
        print('Method exampleName was called')

myObj = myClass()

myMethodName = "exampleName"

method = getattr(myObj, myMethodName)
method('some param')

Update:

You have some more problems in the example you added:

class Test:
    # This was removed, as you already initialize the instance variable in the constructor.
    # Setting it here would cause the variable to be shared by all instances of the class,
    # which you probably do not want, as you are passing the value as argument to the constructor below.
    # name = None

    # You missed to add the self parameter
    def __init__(self, name):
        self.name = name

    # Again, self parameter was missed
    # Additionally, we added a `suffix` parameter, that will accept the `test` value from the `process` call
    # We also added *args and **keyargs that will accept arbitrary number of parameters. Those will be
    # passed down to the `process_test` method.
    def process(self, suffix, *args, **keyargs):
        # We use the `suffix` parameter here, (instead of `name`)
        method = getattr(self, 'process_' + suffix)
        method(*args, **keyargs)

    def process_test(self, param):
        print("process_test(): " + param)


test = Test("cica")
test.process("test", "some param")
quasoft
  • 5,291
  • 1
  • 33
  • 37