3

I want to pass a method foo of an instance of a class A to another function run_function. The method foo will use instance variables of its class A.

Here is a minimal example, in which the instance variable of A is simply self.a_var, and foo just prints that variable.

class A:
    def __init__(self,a_var):
        self.a_var=a_var
    def foo(self):
        print(self.a_var)


class B:
    def __init__(self, A):
        self.A=A

        # EDIT (comment to compile)
        self.A.do_something()

    def run_function(self):
        self.A.foo()
    def run_function_2(self, bar):
        bar()



myA = A(42)
myA.foo()

# Current implementation
myB=B(myA)
myB.run_function()

# Better(?) implementation
myB.run_function_2(myA.foo)

At the moment I pass the instance myA of class A to the instance of B and explicitly call self.A.foo(). This forces the name of the function of Ato be foo. Which is stupid.

The better (?) implementation passes the function of the instance to run_function2. This works, but I am not sure if this is "safe".

Question:

Are there any loopholes that I don't see at the moment?

The important part is, that the method foo, that is passed, needs to access instance variables of the (its) class instance. So, will foo that is called inside run_function_2 always have access to all instance variables of myA?

Is there a better way to implement this?

EDIT: I forgot to add, that class B will always have an instance of A, since it has to do_something with that instance. Maybe that will change something(?). Sorry!

P. Siehr
  • 525
  • 1
  • 6
  • 22

3 Answers3

4

For your second implementation, have you considered the following:

>>> myAa = A(42)
>>> myAb = A(43)
>>> myB = B(myAb)
>>> myB.run_function_2(myAa.foo)
42

This might not be what you want. How about using getattr() and just passing in the desired method name:

>>> class C:
...     def __init__(self, A):
...             self.A = A
...     def run_fct(self, bar):
...             fct = getattr(self.A, bar)
...             fct()
... 
>>> myC = C(myAa)
>>> myC.run_fct('foo')
42
bgse
  • 8,237
  • 2
  • 37
  • 39
  • Thanks for your answer. Indeed, that "43"-issue is a good point. About `getatrr`: I have to play with this feature and read about it, to see its potential. I never used that before. It definitively get's rid of the "43"-issue. – P. Siehr Aug 15 '17 at 14:57
  • @P.Siehr In a more complex scenario, I could imagine the "43" issue even leading to some very nasty debugging sessions as passing a method of a wrong instance might be very hard to spot, so I'd definitely advise strongly against that approach. – bgse Aug 15 '17 at 15:06
  • thank you @bgse: If I could leave a tip, I would :) – Markus Dutschke Apr 08 '20 at 06:20
2

To answer your questions:

  1. Any function executed in the context of an object instance will have access to the instance variables.
  2. There may be a better way to implement this, you could try defining an interface for class A and other classes that might be like it. The you know that the function will always be called foo(). If not, I'd question why it is you need to have some object call an arbitrary method on another object. If you can give more concrete examples about what you're trying to do it would help.
pypypy
  • 1,075
  • 8
  • 18
  • Thanks for your answer. As a mathematician I tried to be as abstract as possible, to not annoy anyone with details. Maybe not the best choice ;) ---- The class `B` is something I call `CrashHandler`. It will call `foo` several (>100) times over several days. If `foo` throws an error the `CrashHandler` will catch it, do a lot of stuff (cleaning), and then call `foo` again. The reason why the `CrashHandler` is not part of `A` is, that other users will program their own class `A` - at least the `foo` part e.g. using inheritance. So the `CrashHandler` is something that they should not touch. – P. Siehr Aug 15 '17 at 15:12
  • I suggest you implement a superclass of class A and provide a required interface which can then be implemented by all subclasses. Include methods like `handle_crash()` and `run()` for example – pypypy Aug 15 '17 at 15:23
  • I will think about that. Thanks for the advice. – P. Siehr Aug 15 '17 at 15:25
0

The main difference between run_function and run_function_2 is that the former calls foo on the object that was given to the B() constructor. run_function_2 is independent of what object is saved as self.A; it just calls the function/method you give it. For example

class A:
    def __init__(self,a_var):
        self.a_var=a_var
    def foo(self):
        print(self.a_var)


class B:
    def __init__(self, A):
        self.A=A
    def run_function(self):
        self.A.foo()
    def run_function_2(self, bar):
        bar()

myA = A(42)
myB = B(myA)
myA2 = A(3.14)

myB.run_function()
myB.run_function_2(myA.foo)
myB.run_function_2(myA2.foo)

Output

42
42
3.14

Are there any loopholes that I don't see at the moment?

These two ways of calling methods are fine. Though I agree that function_run_2 is more convenient since it doesn't fix the method name, it makes you ask... what's the purpose of giving an A object to the B constructor in the first place if it's never used?

The important part is, that the method foo, that is passed, needs to access instance variables of the (its) class instance. So, will foo that is called inside run_function_2 always have access to all instance variables of myA?

Yes. run_function_2 arguments requires a function. In this case, you pass myA.foo, an object myA's method defined in class A. When you call foo inside run_function_2, you are only dealing with attributes variables of the instance myA; this is the idea of encapsulation in classes.

Is there a better way to implement this?

Answering also your question on safety, it's perfectly safe. Functions and methods are objects in Python, and they can be passed around like values. You're basically leaning on the idea of function currying or partial functions. See How do I pass a method as a parameter in Python. These two ways are fine.

Miket25
  • 1,895
  • 3
  • 15
  • 29