0

I have a simple class:

class MyClass():
    
    ... 
    
    def function(self):
        return self.transform(**self.kwargs)
        
    def transform(self, **kw):
        ''' main '''
        return 1

I tried to override the transform method, but when it's called by the function method it fails.

def t(self, **kw):
    return 2

instance = MyClass()
instance.transform = t
instance.function()

> missing 1 required positional argument: 'self'

weird right? I thought the self. denotes that it's passing the self in as the first argument automatically...?

So it works if I don't include self.

def t(**kw):
    return 2

instance = MyClass()
instance.transform = t
instance.function()

> 2

But that means my t function can't access any of the attributes of the object. Can you help me?

Of course I could create a new class that derives from MyClass, but I don't want to. Any other way to inject a method to an object that can access the objects memory?

MetaStack
  • 3,266
  • 4
  • 30
  • 67
  • Methods *belong to a class*. For a function to operate as a method, i.e. passing the instance as the first argument when called on an instance, then the function needs to belong to the class so that the descriptor protocol can do that (which is how that all works). What you are doing isn't overriding, and has nothing to do with inheritance, you are simply *shadowing* the method on the class with an instance attribute that happens to be a function. It's just a function at that point, the same as if it were in the global scope – juanpa.arrivillaga Oct 02 '20 at 18:51
  • @juanpa.arrivillaga I appreciate the clarity but how do I make the method belong to the class? – MetaStack Oct 02 '20 at 18:53
  • 1
    To do something like this, you must manually partially apply the instance to the function object. See the linked duplicate. Something to the effect of `instance.transform = lambda **kw: t(self, **kw)` or `instance.transform = functools.partial(t, instance)` – juanpa.arrivillaga Oct 02 '20 at 18:54
  • 1
    Or, to replicate what function objects do in their descriptor protocol, `instance.transform = types.MethodType(t, instance)` – juanpa.arrivillaga Oct 02 '20 at 18:56
  • 1
    However, are you actually trying to do this at the *instance level*? If not, then just do `MyClass.transform = t` – juanpa.arrivillaga Oct 02 '20 at 18:59
  • @juanpa.arrivillaga the partial solution seems most intuitive to me, any reason to prefer one way of doing this over another, such as the one you indicated or monkey patching? `from functools import partial; instance.transform = partial(t, instance)` – MetaStack Oct 02 '20 at 19:00
  • @juanpa.arrivillaga yes, at the instance level, I'm going to import MyClass once, build instances and change how that one function works in different ways on all of the objects. so monkey patching is probably not the best for that, but it could work. – MetaStack Oct 02 '20 at 19:02
  • Note that you can change an object's type retroactively, and generate a new class with an arbitrary set of superclasses. Mind, it's been way back in the day (pre-Python 3) since I ever actually _did_ anything that hacky... – Charles Duffy Oct 02 '20 at 19:03
  • 1
    Not sure exactly which one you are referring to as monkey-patching, `MyClass.transform = t`? That is different. That changes the method for *all instances of the class*. There might be implications with regards to the docstrings, etc for choosing `types.MethodType`, but they are all fundamentally the same solution, shadowing the class attribute with an instance attribute that partially applies the instance as the first argument to the function. I would just design the class to accept a transform as a dependency and do something like `return self.transform(self, *args, **kwargs)` in `function` – juanpa.arrivillaga Oct 02 '20 at 19:04

0 Answers0