I have a number of classes where most methods merely ‘rewire’ method calls to self.value
, and return a new instance:
class someClass(): def __init__(self, value): self.value = value def __add__(self, other): return self.__class__( self.value.__add__(other) ) def someMethod(self, *args, **kwargs): return self.__class__( self.value.someMethod(*args, **kwargs) ) def someOtherMethod(self, *args, **kwargs): return self.__class__( self.value.someOtherMethod(*args, **kwargs) )
Of course, not all methods inside are like this, but most are.
Instead of having to implement explicitly someMethod
, someOtherMethod
and __add__
, is there a way to do this systematically? Perhaps with __getattr__
or __getattribute__
?
Here is why subclassing the value’s type is not viable :
>>> class someClass(int): pass >>> a = someClass(5) >>> isinstance(a+5, someClass) False #I need this to be True
In order to make that last line return True, I would have to ‘rewire’ all operators like before : subclassing doesn’t help at all
Advertisement
Answer
A partial answer, because you still have to list the methods that you want to wrap, but the following makes it a lot easier to wrap a large number of methods, because you just add them to a list of methods to be wrapped:
class MyClass(): def __init__(self, value): self.value = value def __str__(self): return f'<MyClass({repr(self.value)})>' def make_wrappers(cls, methods): for method in methods: def wrapper(self, *args, _method=method, **kwargs): return self.__class__(getattr(self.value, _method)(*args, **kwargs)) setattr(cls, method, wrapper) make_wrappers(MyClass, ['__add__', '__sub__', 'upper']) x = MyClass(3) print(x+2, x-2) # <MyClass(5)> <MyClass(1)> y = MyClass('hello') print(y.upper()) # <MyClass('HELLO')>
It does not seem that you can use __getattr__
to do entirely what you want. This is because if you are trying to evaluate a+b
, where a
is an instance of your class, then if you have not defined an __add__
method but have defined __getattr__
, then this will raise a TypeError
and ignore (not call) __getattr__
. Therefore you cannot use __getattr__
to wrap “magic methods” such as __add__
(at least in Python 3).
Note here that the _method
is used to ensure that value of the variable method
is bound into the wrapper at the time when it is defined (it is added to the defaults dictionary). If instead you were to use method
directly inside wrapper
where _method
is used, then you would find that every wrapper would use the value of method
inherited from an outer scope when it is called, which would not be what you want.
Note also that the solution relies on “monkey-patching” the class. This will work. What will not work in Python 3 is to monkey-patch instances of the class — for example if you tried to use setattr
to add a method directly to x
that does not exist in MyClass
, then calling it would not work.