3

I want to pass an instance method to another function but when I do I don't know from which instance that will be. I only know the base class from which those instances are derived. Is there a way to call the instance method from inside the function?

I have the following code:

class Base:
    def do_something():
        pass        


class InstanceOne(Base):
    def do_something():
        print("Instance One")


class InstanceTwo(Base):
    def do_something():
        print("Instance Two")


def my_function(instances_list: List[Base], method)
    for instance in instances_list:
        instance.method()


# The main part
my_list = [InstanceOne(), InstanceTwo(), InstanceTwo(), InstanceOne()]
my_function(my_list, Base.do_something)

The code above won't work because the function do_something is called on the Base class and not on the instances. Is there a way to actually call the instances's methods do_something when the function (my_function) doesn't know what the instances will be?

6
  • 3
    More than that, you're calling the method named ".method" on all instances. The parameter method is never used. Commented Sep 24, 2019 at 9:17
  • @deceze: yes you are absolutely right. I used this code to make my problem clear. If I would only have called method then maybe it wasn't clear I wanted to call the instances method. Commented Sep 24, 2019 at 9:30
  • The code does not work for reasons unrelated to what you say and the instances are known both to the main program and my_function. What is preventing you from just calling instance.do_something() on each element of the list (other than the method definition being wrong)? Commented Sep 24, 2019 at 9:45
  • @Goyo: this was a simplification with more code surrounding the loop in my_function. Commented Sep 24, 2019 at 13:22
  • So what? Do you want my_function to call .do_something() on each instance? Do you want something else? Commented Sep 24, 2019 at 13:34

4 Answers 4

6

You can use operator.methodcaller

def my_function(instances_list: List[Base], method_name):
    method = methodcaller(method_name)
    for instance in instances_list:
        method(instance)

Also your functions are methods, they need to accept a self argument.

Your code should look as follows:

from operator import methodcaller

class Base:
    def do_something(self):
        pass        


class InstanceOne(Base):
    def do_something(self):
        print("Instance One")


class InstanceTwo(Base):
    def do_something(self):
        print("Instance Two")


def my_function(instances_list, method_name):
    method = methodcaller(method_name)
    for instance in instances_list:
        method(instance)


# The main part
my_list = [InstanceOne(), InstanceTwo(), InstanceTwo(), InstanceOne()]
my_function(my_list, 'do_something')

This outputs:

Instance One
Instance Two
Instance Two
Instance One
Sign up to request clarification or add additional context in comments.

Comments

3

this is because you do explicitly specify to use the method found in Base.

I propose two ways to solve it:

Use lambda

You can use an anonymous lambda function to do what you want:

def my_function(instances_list: List[Base], method)
    for instance in instances_list:
        method(instance)

# The main part
my_list = [InstanceOne(), InstanceTwo(), InstanceTwo(), InstanceOne()]
my_function(my_list, lambda i: i.do_something())

Use getattr

Another way to solve it in a more abstract manner is by using the getattr function and passing the method's name as a string.

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.

def my_function(instances_list: List[Base], method_name)
    for instance in instances_list:
        getattr(instance, method_name)()

# The main part
my_list = [InstanceOne(), InstanceTwo(), InstanceTwo(), InstanceOne()]
my_function(my_list, 'do_something')

Comments

1

Yes you can by using hasattr and getattr

def my_function(instances_list: List[Base], method)
    for instance in instances_list:
        if hasattr(instance, method):
            getattr(instance, method)()

and call your function like

# The main part
my_list = [InstanceOne(), InstanceTwo(), InstanceTwo(), InstanceOne()]
my_function(my_list, 'do_something')

4 Comments

if hasattr(instance, method): why? this should probably not fail silently.
And if it should fail silently I'd probably go with getattr(instance, method, lambda: pass)() instead.
@beer44 You should rename method to method_name.
@deceze , yes in this way it will fail silently and indeed you have suggested the best way way to do as one liner
1

Use the actual method name through method_object.__name__.
See the corrected approach below:

from typing import List

class Base:
    def do_something(self):
        pass


class InstanceOne(Base):
    def do_something(self):
        print("Instance One")


class InstanceTwo(Base):
    def do_something(self):
        print("Instance Two")


def my_function(instances_list: List[Base], method_name):
    for instance in instances_list:
        getattr(instance, method_name)()


# The main part
my_list = [InstanceOne(), InstanceTwo(), InstanceTwo(), InstanceOne()]
my_function(my_list, Base.do_something.__name__)

The output:

Instance One
Instance Two
Instance Two
Instance One

2 Comments

May I suggest that this solution, while technically correct, is a bit convoluted (Rube Goldberg anyone ?) and not very efficient (wrt/ directly passing the method name as string) ?
@brunodesthuilliers, not a big deal for such kind of declaim. You don't know whether that method object is planned to be somehow used further. I've changed to pass a method name so the community could be more satisfied (if it even cares)

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.