Adding hooks to functions in subclassed methods - python

Given the following simplified code:
from abc import ABC, abstractmethod
class Parent(ABC):
def __init__(self,*args,**kwargs):
self.parent_name = 'SuperClass'
# global hook to run before each subclass run()
def global_pre_run_hook(self):
pass
#abstractmethod
def run(self, *args, **kwargs):
raise NotImplementedError()
class Child(Parent):
def __init__(self,*args,**kwargs):
super().__init__(*args,**kwargs)
self.name = 'ChildClass'
def run(self):
print(f'my parent name is {self.parent_name}')
print(f'my name is {self.name}')
return 22
obj = Child()
result = obj.run()
Is there a way to add functionality so that when the child class run() method is called directly, it first triggers a hook function from the parent class? Assume there is a parent class and a lot of classes that subclass it - would I need to manually add a call global hook() at the beginning of each run() definition for each class that subclasses Parent()? Is there a pythonic way to accomplish this?

There might be a way to do this with a proper decorator, but for the time being I think the cleanest solution you might come up with would be to create your own 'decorator' and manually apply it in the course of Parent.__init__(), which would make sure it takes effect so long as the child class invokes the parent __init__():
from abc import ABC, abstractmethod
def create_hook(func, hook):
def wrapper(*args, **kwargs):
hook()
return func(*args, **kwargs)
return wrapper
class Parent(ABC):
def __init__(self, *args, **kwargs):
self.parent_name = 'SuperClass'
self.run = create_hook(self.run, self.global_pre_run_hook)
# global hook to run before each subclass run()
def global_pre_run_hook(self):
print("Hooked")
#abstractmethod
def run(self, *args, **kwargs):
raise NotImplementedError()
class Child(Parent):
def __init__(self,*args,**kwargs):
super().__init__(*args,**kwargs)
self.name = 'ChildClass'
def run(self):
print(f'my parent name is {self.parent_name}')
print(f'my name is {self.name}')
return 22
obj = Child()
result = obj.run()
# this prints:
# Hooked
# my parent name is SuperClass
# my name is ChildClass

The answer by Green Cloak Guy works, but cannot be pickled! To fix this, we need to move the hook creation into __new__. Also, it's a good idea to make use of functools.wraps in the hook creator.
import pickle
from abc import ABC, abstractmethod
from functools import wraps
def create_hook(func, hook):
#wraps(func)
def wrapper(*args, **kwargs):
hook(*args, **kwargs)
return func(*args, **kwargs)
return wrapper
class Parent(ABC):
def __new__(cls, *args, **kwargs):
cls.run = create_hook(cls.run, cls.global_pre_run_hook)
return super().__new__(cls, *args, **kwargs)
# global hook to run before each subclass run()
def global_pre_run_hook(self, *args, **kwargs):
print("Hooked")
#abstractmethod
def run(self, *args, **kwargs):
raise NotImplementedError()
class Child(Parent):
def run(self):
print(f"my parents are {self.__class__.__mro__}")
print(f"my name is {self.__class__.__name__}")
return 22
obj = Child()
result = obj.run()
pickle.dumps(obj)

Related

Accessing class property as decorator argument

I'm trying to apply a conditional decorator as described in another stackoverflow post, but I'd like the condition to be set from inside the class its being used. Instead I get a Reference error pointing that self is not defined.
class foo:
def __init__(self):
self.debug = True
#conditional_decorator(decorator, self.debug)
def function(self):
pass
I tried defining a global variable and updating it from inside the __init__() method but it kept its original value when called as an argument of the decorator.
debug = None
class foo:
def __init__(self):
self.debug = True
global debug
debug = self.debug
#conditional_decorator(decorator, debug)
def function(self):
pass
The only way it worked was declaring a global variable and setting it outside of the class.
How can I apply the value of the class property to the decorator?
An update to the answer given by #Maurice Meyer which allows a member of the class to be nominated:
from functools import wraps
def conditional_decorator(decoration, member):
def decorator(method):
predecorated = decoration(method)
#wraps(method)
def wrapper(*args, **kwargs):
self = args[0]
condition = getattr(self, member)
if not condition:
return method(*args, **kwargs)
return predecorated(*args, **kwargs)
return wrapper
return decorator
#And used like this for example:
class foo:
def __init__(self, debug):
self.debug = debug
#conditional_decorator(decorator, "debug")
def function(self):
pass
f1 = foo(True)
f1.function()
This is how you make a decorator handle classes and arguments:
from functools import wraps
def conditional_decorator(param):
def real_decorator(fn):
#wraps(fn)
def wrapper(*args, **kw):
cls = args[0]
print(cls.debug)
print(param)
return wrapper
return real_decorator
class foo:
def __init__(self):
self.debug = True
#conditional_decorator('param1')
def function(self):
pass
f = foo()
f.function()
Output:
True
param1
The decorator should not be conditional. Rather, when the decorated function is called, it should look at self.debug to determine whether to use the original function or the wrapped part.
def conditional_decorator(dec):
def decorator(func):
def _(self, *args, **kwargs):
f = func
if self.debug:
f = dec(f)
return f(self, *args, **kwargs)
return _
return decorator
def decorator(f):
def _(*args, **kwargs):
print("Decorated")
return f(*args, **kwargs)
return _
class foo:
def __init__(self, debug):
self.debug = debug
#conditional_decorator(decorator)
def function(self):
print("foo stuff")
foo(True).function()
print("===")
foo(False).function()
outputs
Decorated
foo stuff
===
foo stuff

How does Python resolve decorator names

I have the following code:
import threading
from functools import wraps
class Synchronized(object):
def __init__(self):
self.lock = threading.Lock()
def synchronized(f):
#wraps(f)
def wrapper(self, *args, **kwargs):
with self.lock:
print "here"
return f(self, *args, **kwargs)
return wrapper
#synchronized
def go(self):
print 1
class B(Synchronized):
#synchronized
def foo(self):
return 1
This code fails on import complaining:
File "a.py", line XXX, in B
#synchronized
NameError: name 'synchronized' is not defined
However if I comment out B and just use Syncrhonized().go() it works great.
Question: How does python know what is #synchronized in base class but fails to resolve it in its derivatives?
synchronized is defined as a function in the class body of Synchronized only.
A class body is executed like a function to define the class; the resulting local namespace is used to form the class attributes. This is why synchronized is still a local name within Synchronized when used as a decorator on go. You could compare that with defining the decorator inside a function, then trying to apply it outside of that function; it won't work, because the decorator is a local variable.
You could use #Syncronized.synchronized.im_func in class B (.im_func to unwrap the function from the method wrapper):
class B(Synchronized):
#Synchronized.synchronized.im_func
def foo(self):
return 1
Better still, do not defined the decorator inside a class at all but instead defined it outside of Synchronized. It is not a method, after all:
def synchronized(f):
#wraps(f)
def wrapper(self, *args, **kwargs):
with self.lock:
print "here"
return f(self, *args, **kwargs)
return wrapper
class Synchronized(object):
def __init__(self):
self.lock = threading.Lock()
#synchronized
def go(self):
print 1
class B(Synchronized):
#synchronized
def foo(self):
return 1

Methods decorated with a decorator class do not have the "self" argument frozen

I have a decorator declared as a class:
class predicated(object):
def __init__(self, fn):
self.fn = fn
self.fpred = lambda *args, **kwargs: True
def predicate(self, predicate):
self.fpred = predicate
return self
def validate(self, *args, **kwargs):
return self.fpred(*args, **kwargs)
def __call__(self, *args, **kwargs):
if not self.validate(*args, **kwargs):
raise PredicateNotMatchedError("predicate was not matched")
return self.fn(*args, **kwargs)
... and when I use it to wrap a method in a class, calling that method does not seem to set the instance of the object as the first argument. While this behavior is not exactly unexpected, how would I go about getting self to be frozen when the method becomes an instance method?
Simplified example:
class test_decorator(object):
def __init__(self, fn):
self.fn = fn
def __call__(self, *args, **kwargs):
return self.fn(*args, **kwargs)
class Foo(object):
#test_decorator
def some_method(self):
print(self)
Foo().some_method()
Expected instance of foo, instead get an error saying 0 arguments were passed.
Figured it out - needed to define a __get__ method in order to create a MethodType binding like so:
def __get__(self, obj, objtype=None):
return MethodType(self, obj, objtype)
which creates a MethodType object when invoking the method on an object that freezes the self argument.

Should I be using abstract methods in this Python scenario?

I'm not sure my approach is good design and I'm hoping I can get a tip. I'm thinking somewhere along the lines of an abstract method, but in this case I want the method to be optional. This is how I'm doing it now...
from pymel.core import *
class A(object):
def __init__(self, *args, **kwargs):
if callable(self.createDrivers):
self._drivers = self.createDrivers(*args, **kwargs)
select(self._drivers)
class B(A):
def createDrivers(self, *args, **kwargs):
c1 = circle(sweep=270)[0]
c2 = circle(sweep=180)[0]
return c1, c2
b = B()
In the above example, I'm just creating 2 circle arcs in PyMEL for Maya, but I fully intend on creating more subclasses that may or may not have a createDrivers method at all! So I want it to be optional and I'm wondering if my approach is—well, if my approach could be improved?
You still have a problem, when you will inherit your class B, and this will call A.__init__ and if you don't implement createDrivers in the subclass this line callable(self.createDrivers) will throw an error as that createDrivers doesn't exist (AttributeError) i think if i were you i will do it like so:
class A(object):
def __init__(self, *args, **kwargs):
try:
self._drivers = self.createDrivers(*args, **kwargs)
select(self._drivers)
except NotImplementedError:
pass
def createDrivers(self, *args, **kwargs):
raise NotImplementedError("This class wasn't implemented")
class B(A):
def createDrivers(self, *args, **kwargs):
c1 = circle(sweep=270)[0]
c2 = circle(sweep=180)[0]
return c1, c2
class C(A):
pass
Another way is to replace callable(self.createDrivers) by hasattr(self, 'createDrivers').
I would do this:
class A(object):
def __init__(self, *args, **kwargs):
self.createDrivers(*args, **kwargs)
def createDrivers(self, *args, **kwargs):
"Override"
pass
class B(A):
def createDrivers(self, *args, **kwargs):
self._drivers = blabla
If you want createDrivers to be optional but still always there, the best is not an abstract method, but do implement it in the base class as a noop.
class A(object):
def __init__(self, *args, **kwargs):
self._drivers = self.createDrivers(*args, **kwargs)
select(self._drivers)
def createDrivers(self, *args, **kwargs):
"""This should be overridden by subclasses if they need custom drivers"""
pass

Python: How do I access an decorated class's instance from inside a class decorator?

Here's an example of what I mean:
class MyDecorator(object):
def __call__(self, func):
# At which point would I be able to access the decorated method's parent class's instance?
# In the below example, I would want to access from here: myinstance
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
class SomeClass(object):
##self.name = 'John' #error here
name="John"
#MyDecorator()
def nameprinter(self):
print(self.name)
myinstance = SomeClass()
myinstance.nameprinter()
Do I need to decorate the actual class?
class MyDecorator(object):
def __call__(self, func):
def wrapper(that, *args, **kwargs):
## you can access the "self" of func here through the "that" parameter
## and hence do whatever you want
return func(that, *args, **kwargs)
return wrapper
Please notice in this context that the use of "self" is just a convention, a method just uses the first argument as a reference to the instance object:
class Example:
def __init__(foo, a):
foo.a = a
def method(bar, b):
print bar.a, b
e = Example('hello')
e.method('world')
The self argument is passed as the first argument. Also your MyDecorator is a class emulating a function. Easier to make it an actual function.
def MyDecorator(method):
def wrapper(self, *args, **kwargs):
print 'Self is', self
return method(self, *args, **kwargs)
return wrapper
class SomeClass(object):
#MyDecorator
def f(self):
return 42
print SomeClass().f()

Categories

Resources