4

EDIT: I extended the example to show the more complex case that I was talking about before. Thanks for the feedback in the comments.

Some more context:

  • I am mostly interested in this on a theoretical level. I would be happy with an answer like "This is not possible because ...", giving some detailed insights about python internals.
  • For my concrete problem I have a solution, as the library allows to pass a class which then can do what I want. However, if there would be a way to use the simpler interface of just passing the function which gets bound, I would save many lines of code. (For the interested: This question is derived from the sqlalchemy extension associationproxy and the creator function parameter.)
# mylib.py
from typing import Callable

class C:
    def __init__(self, foo: Callable):
        self.foo = foo

    def __get__(self, instance, owner):
        # simplified
        return CConcrete(self.foo)

class CConcrete:
    foo: Callable

    def __init__(self, foo: Callable):
        self.foo = foo

    def bar(self):
        return self.foo()
# main.py
from mylib import C

def my_foo(self):
    return True if self else False

class House:
    window = C(my_foo)

my_house = House()
print(my_house.window.bar())

This code gives the error

my_foo() missing 1 required positional argument: 'self'

How can I get my_foo be called with self without changing the class C itself?

The point is that a class like this exists in a library, so I can't change it.
In fact it's even more complicated, as foo gets passed down and the actual object where bar exists and calls self.foo is not C anymore. So the solution can also not include assigning something to c after creation, except it would also work for the more complex case described.

Alexander
  • 310
  • 2
  • 13
  • 3
    Have you tried deleting `self` from `my_foo`? – Mechanic Pig Aug 11 '23 at 15:44
  • 1
    Is that intended to be `foo: Callable` in the class definition? – tadman Aug 11 '23 at 15:45
  • 1
    `foobar` doesn't do anything here – wjandrea Aug 11 '23 at 15:46
  • 3
    If `my_foo` doesn't need `self` for anything (as in this example where it just returns `True`), remove the `self` argument. If it does, please update your minimal example such that it uses the `self` argument, so that your example is representative of your usage – Pranav Hosangadi Aug 11 '23 at 15:47
  • 5
    Do the answers to this [question](https://stackoverflow.com/questions/972/adding-a-method-to-an-existing-object-instance-in-python) help at all? – quamrana Aug 11 '23 at 15:47
  • 1
    Binding the `self` argument using any of the methods shown in [this answer](https://stackoverflow.com/a/28060251/843953) would still fail with your more complex case since the `C` object would be bound and you don't seem to want that – Pranav Hosangadi Aug 11 '23 at 15:52
  • 2
    Can you give some more details about the inheritance hierarchy? Are you able to write a class which inherits from the child class that you are using? – quamrana Aug 11 '23 at 16:01
  • Thanks for the comments. I updated the question to be more clear what my goal with this question is and what the more complex case is. – Alexander Aug 14 '23 at 09:25

1 Answers1

2

You can bind the method to the instance manually, since it won't be living in the class __dict__:

self.foo = foo.__get__(self)

This is assuming you don't want to monkeypatch the entire class. If you do, assign c.foo = my_foo instead of passing to the instance initializer.

You could also conceivably use inheritance:

class C(C):
   def __init__(self):
       super().__init__(self.foo)

   def foo(self):
       return True
Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
  • Thanks. That however would change the class of the library which I can't do. Similar problem with monkey-patching, as I want the original behaviour in a different place. – Alexander Aug 14 '23 at 09:32
  • @Alexander. The second method will work if your code uses the wrapper object most of the time – Mad Physicist Aug 14 '23 at 12:30
  • Hi, then I seem to get it wrong. With my extended example, adding ``` class MyC(C): def foo(self): return True if self else False ``` And changing to `window = MyC(my_foo)` still fails. – Alexander Aug 14 '23 at 16:51
  • @Alexander. That's because `window = MyC()` is all you need. The monkey-patching is handled by the child class. – Mad Physicist Aug 14 '23 at 17:04
  • @Alexander. But if you need to pass in the method, make sure it's bound: `window = MyC(); window.foo = my_foo.__get__(window)` – Mad Physicist Aug 14 '23 at 17:07
  • 1
    I then get `TypeError: C.__init__() missing 1 required positional argument: 'foo'`. Adding `def __init__(self): super().__init__(self.foo)` does the trick. I think that's reasonable as I don't need to copy content of the original init. – Alexander Aug 15 '23 at 07:31
  • @Alexander. Fair point. I updated the suggestion in my answer to match what you discovered. – Mad Physicist Aug 15 '23 at 13:52