osdir.com

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Injecting methods into instance / class


duncan smith wrote:

> Hello,
>       I have a lot of functions that take an instance of a particular
> class as the first argument. I want to create corresponding methods in
> the class. I have tried the following, which (when called from __init__)
> creates the relevant methods in an instance (Python 3.6).
> 
> 
> def init_methods(self):
>     for func_name, method_name in [('add', '__add__'),
>                                    ('subtract', '__sub__')]:
>         setattr(self, method_name,
> types.MethodType(globals()[func_name], self))
> 
> 
> The problem is that e.g.
> 
> x.__sub__(y)
> 
> works as expected, but
> 
> x - y
> 
> does not (because special methods are looked up in the class rather than
> the instance).
> 
> I have tried to find examples of injecting methods into classes without
> success. I have tried a few things that intuition suggested might work,
> but didn't - like removing the first line above, dedenting and replacing
> self with the class. This is only to save typing and make the code
> cleaner, but I would like to get it right. Any pointers appreciated. TIA.

You can do this in the __ init__() method:

$ cat tmp.py
def add(self, other):
    return 42 + other

def init_methods(self):
    cls = type(self)
    for methodname, funcname in [("__add__", "add")]:
        func = globals()[funcname]
        setattr(cls, methodname, func)

class Demo:
    def __init__(self):
        init_methods(self)

x = Demo()
print(x + 100)
$ python3 tmp.py
142

but the __add__() method (to take the example above) is shared by all 
instances. You are either doing too much work by setting it again and again 
in every Demo.__init__() call or you are changing the behaviour of 
previously initialised Demo instances (if the last init_methods() sets the 
method to a different function) and confuse yourself.

Have you considered a mix-in class instead? Like

$ cat tmp.py
def add(self, other):
    return 42 + other

class CommonMethods:
    __add__ = add

class Demo(CommonMethods):
    pass

x = Demo()
print(x + 100)
$ python3 tmp.py
142

This is much cleaner, and if you insist you can set up CommonMethods 
programmatically with

CommonMethods = type(
    "CommonMethods", (),
    {m: globals()[f] for m, f in [("__add__", "add")]}
    # without globals() and probably better: 
    # {m: f for m, f in [("__add__", add)]}
)

though personally I don't see the point.