I want to be able to dynamically override all calls sent to methods on the parent class. So far I've only been able to do it for methods not defined in a parent class.
I.e. Given a parent class:
class A:
def foo(self):
print("foo")
def bar(self):
print("bar")
I want to create a Spy
class that inherits from A
will print "This is a spy"
before calling any method on A
, including foo
.
class Spy(A):
pass # What do I do here?
s = Spy()
>>> s.foo()
This is a spy
foo
Current implementation
My current implementation of Spy
is this:
class Spy(A):
def __getattr__(self, method):
print("This is a spy")
return getattr(super(A, self), method)
However, this only works for methods not defined in the parent class:
>>> s.baz()
This is a spy
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in __getattr__
AttributeError: 'super' object has no attribute 'baz'
When I call a method that exists already, it doesn't work:
>>> s.foo()
foo
>>> s.bar()
bar
CodePudding user response:
The following code snippet should do what you want. I have excluded methods starting with __
because these can be problematic (for example, overriding __class__
with a function will cause an error).
class A:
def foo(self):
print("foo")
def bar(self, x):
print(x)
class Spy(A):
def functionwrapper(self, functionname):
originalfunc = getattr(super(), functionname)
def wrap(*args, **kwargs):
print("This is a spy: ", end="")
originalfunc(*args, **kwargs)
return wrap
def __init__(self):
for methodname in [method for method in dir(A) if (method[0:2] != "__")]:
setattr(self, methodname, self.functionwrapper(methodname))
s = Spy()
s.foo()
s.bar("variable")
Output
This is a spy: foo
This is a spy: variable
CodePudding user response:
When I call a method that exists already, it doesn't work
That's because __getattr__
is only gonna get called when the default attribute access fails. So if the parent has that method, it gets found and your __getattr__
won't get called.
You need to intercept the __getattribute__
instead:
from types import MethodType
from inspect import isfunction
class A:
def foo(self):
print("foo")
class Spy(A):
def bar(self):
print("barrrrrrrrr")
def __getattribute__(self, name):
# check it instance's namespace
instance_dict = object.__getattribute__(self, "__dict__")
if name in instance_dict:
return instance_dict[name]
# check its class' namespace
if name in type(self).__dict__:
return object.__getattribute__(self, name)
# check parents
for cls in type(self).mro()[1:]:
if name in cls.__dict__:
member = cls.__dict__[name]
if isfunction(member):
# your code here
print("This is a spy")
return MethodType(cls.__dict__[name], self)
return member
raise AttributeError(f"{type(self)} object has no attribute '{name}'")
s = Spy()
s.foo()
s.bar()
print("-----------------------------------")
s.boo()
output:
This is a spy
foo
barrrrrrrrr
-----------------------------------
Traceback (most recent call last):
File "...", line 32, in <module>
s.boo()
File "...", line 25, in __getattribute__
raise AttributeError(f"{type(self)} object has no attribute '{name}'")
AttributeError: <class '__main__.Spy'> object has no attribute 'boo'
So this is one way of doing it and I agree that it might be overkill.
You do that in three steps:
Check to see it is in the instance's namespace: like the data attributes you usually set in
__init__
.Check to see it is in its class' namespace: You don't want yout print statement here because it's not sent to the parent.
Check its parents
CodePudding user response:
test.py:
import functools
import inspect
TXT = "This is a spy"
def deco(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(TXT)
return func(*args, **kwargs)
return wrapper
class A:
def foo(self):
print("foo")
def bar(self):
print("bar")
class Spy(A):
def __init__(self):
self.patch()
@classmethod
def patch(cls):
superclass = cls.__bases__[0]
for name, value in inspect.getmembers(superclass, inspect.isfunction):
setattr(cls, name, deco(value))
def main():
s = Spy()
s.foo()
s.bar()
if __name__ == "__main__":
main()
Test:
$ python test.py
This is a spy
foo
This is a spy
bar