Python Trigger a function when a function is called - python

So, I am making a desktop app with PyQt6. I have a stylesheet and I want it to update every time I add something to it. Stylesheet is an array and I have multiple functions adding things to it:
def set_color(self, color):
self.stylesheet.append(f"color: {color}")
def set_background_color(self, color):
self.stylesheet.append(f"background-color: {color}")
def set_border_radius(self, radius):
self.stylesheet.append(f"border-radius: {radius}px")
def set_alignment(self, alignment):
self.stylesheet.append(f"text-align: {alignment}")
Now, I want to update the stylesheet every time I call one of these functions (or a function in the class as a whole) by calling another function that updates it. But I don't want to add it manually to every function. Is there a better way of doing this?
def update_stylesheet(self):
result = ""
for css in self.stylesheet:
if css.strip():
result += css.strip()
result += "; "
self.setStyleSheet(result)

Simple solution: Use function composition.
Instead of calling self.stylesheet.append you could make a helper method, that will call self.stylesheet.append and also call update_stylesheet
def set_color(self, color):
self.add_style(f"color: {color}")
def set_background_color(self, color):
self.add_style(f"background-color: {color}")
def set_border_radius(self, radius):
self.add_style(f"border-radius: {radius}px")
def set_alignment(self, alignment):
self.add_style(f"text-align: {alignment}")
def add_style(self, new_style):
self.stylesheet.append(new_style)
self.update_stylesheet()
More complex solution: Function decorators.
In this case we would make some assertions about what methods the class has present. Using the decorator, we can call the normal method and have extra logic run afterwards. Using the decorator we can hijack a reference to self and call the update_stylesheet method this way.
def update_method(func):
def wrapper(self, *args, **kwargs):
res = func(self, *args, **kwargs)
self.update_stylesheet()
return res
return wrapper
#update_method
def set_color(self, color):
self.stylesheet.append(f"color: {color}")
#update_method
def set_background_color(self, color):
self.stylesheet.append(f"background-color: {color}")
#update_method
def set_border_radius(self, radius):
self.stylesheet.append(f"border-radius: {radius}px")
#update_method
def set_alignment(self, alignment):
self.stylesheet.append(f"text-align: {alignment}")

Related

Method decorator to register callbacks

I'm working on a callback system in Python. The to-be callbacks are methods of a class. I register these with a decorator.
Here is some pseudo class representing this:
class MyClass(ABC):
def __init__(self):
self.cb_stack = {}
def register_callback(self, callback, name):
self.cb_stack[name] = callback
#register_callback('mycb')
def myCallback(self, input):
self.do_things()
I suspect each decorator is executed by default.
I would like however for the decorator to be executed when obj.myCallback() is called, and to prevent myCallback() from executing at that moment (which should be the case here).
The alternative I have is to write it in the long form for each callback, which should work like so:
class MyClassLong(ABC):
def __init__(self):
self.cb_stack = {}
def register_callback(self, callback, name):
self.cb_stack[name] = callback
def myCallback(self):
def func(self, input):
self.do_things()
self.register_callback(func, 'mycb')
Notice it adds two lines of codes, so it's not really an issue but more how to make something clean.
Does anyone knows how if it is achievable, and how ?
So both of your versions have an argument input that is ignored but to a different function both times, so I don't know what you want exactly, but if we take that out, you could simpy use:
class MyClass(ABC):
def __init__(self):
self.cb_stack = {}
def register_callback(self, callback, name):
self.cb_stack[name] = callback
def myCallback(self):
self.register_callback(self.do_things, 'mycb')
class and method definitions are processed only once when the class is declared so you can only use decorators at the class level (not at the instance level). If your list of callbacks applied to all instances of the class, you could define a parameterized decorator to register them:
# defines a callback list as a dictionary
# provides the decorator
class Callbacks(dict):
def register(self,name):
def decorate(f):
self[name] = f
return f
return decorate
class MyClass:
cb_stack = Callbacks()
#cb_stack.register('mycb')
def myCallback(self, param):
print("myCallback called",param)
def triggerCallbacks(self):
for n,f in type(self).cb_stack.items(): f(self,n)
mc = MyClass()
mc.triggerCallbacks()
myCallback called mycb
You can also use this for subclasses:
class MySubclass(MyClass):
cb_stack = Callbacks(MyClass.cb_stack)
#cb_stack.register('mycb2')
def myCallback2(self, param):
print("myCallback2 called",param)
msc = MySubclass()
msc.triggerCallbacks()
myCallback called mycb
myCallback2 called mycb2

How to replace every function call with the wrapper call in the original class?

I have two classes, one of which is a wrapper of the other. A function in the original class uses a method called forward, but I want it to use the forward method of the wrapper class after it has been wrapped, not the original. For example:
class A:
def __init__(self):
self.a = 1
def forward(self, x):
return self.a + x
def main(self, x):
return self.forward(x) + 100
class Wrapper:
def __init__(self, A):
self.module = A
def forward(self, x):
# Long convoluted code.
# ..
# ..
return self.module.forward(x)
classA = A()
wrapperA = Wrapper(classA)
# Goal: Make classA.main(..) use the forward function from Wrapper instead.
Because the wrapper class has the long and convoluted code that needs to be run, I want all calls of forward from main to be such that it calls the forward from the wrapper class, not from the original.
Is there a way to do this in Python?
Reasons why I did not use inheritance:
Instantiating class A is memory intensive. If I receive class A object as input, I want to modify its core behavior. without instantiating another object.
classA can be of different object types in runtime.
--
An alternative way I thought of is to redefine main in Wrapper. However, the problem is doing this automatically for every method defined in A without hard coding.
In Python "everything is an object". Including classes, functions, and methods
on objects.
As such we can take any class, loop over all functions in that class and modify
them as needed.
Depending on the real code, the problem in the question might be better tackled
using decorators or meta-classes, depending on the dependencies of the wrapper
(what values does it need access to). I will not go into meta-classes as most needs for meta-classes can also be implemented using class-decorators, which are less error-prone.
As you mention in one of your comments that you may have several different classes that need to be wrapped the class-decorator solution might be a good candidate. This way you won't lose the inheritance tree of the wrapped class.
Here is an example not using either, but doing exactly as asked ;)
Using __new__
from functools import update_wrapper
class A:
def __init__(self):
self.a = 1
def forward(self, x):
"""
docstring (to demonstrate `update_wrapper`
"""
print("original forward")
return self.a + x
def main(self, x):
return self.forward(x) + 100
class Wrapper:
# Using __new__ instead of __init__ gives us complete control *how* the
# "Wrapper" instance is created. We use it to "pull in" methods from *A*
# and dynamically attach them to the `Wrapper` instance using `setattr`.
#
# Using __new__ is error-prone however, and using either meta-classes or
# even easier, decorators would be more maintainable.
def __new__(cls, A):
# instance will be our instance of thie `Wrapper` class. We start off
# with no defined functions, we will add those soon...
instance = super().__new__(cls)
instance.module = A
# We now walk over every "name" in the wrapped class
for funcname in dir(A):
# We skip anything starting with two underscores. They are most
# likely magic methods that we don't want to wrap with the
# additional code. The conditions what exactly we want to wrap, can
# be adapted as needed.
if funcname.startswith("__"):
continue
# We now need to get a reference to that attribute and check if
# it's callable. If not it is a member variable or something else
# and we can/should skip it.
func = getattr(A, funcname)
if not callable(func):
continue
# Now we "wrap" the function with our additional code. This is done
# in a separate function to keep __new__ somewhat clean
wrapped = Wrapper._wrap(func)
# After wrapping the function we can attach that new function ont
# our `Wrapper` instance
setattr(instance, funcname, wrapped)
return instance
#staticmethod
def _wrap(func):
"""
Wraps *func* with additional code.
"""
# we define a wrapper function. This will execute all additional code
# before and after the "real" function.
def wrapped(*args, **kwargs):
print("before-call:", func, args, kwargs)
output = func(*args, **kwargs)
print("after-call:", func, args, kwargs, output)
return output
# Use "update_wrapper" to keep docstrings and other function metadata
# intact
update_wrapper(wrapped, func)
# We can now return the wrapped function
return wrapped
class Demo2:
def foo(self):
print("yoinks")
classA = A()
otherInstance = Demo2()
wrapperA = Wrapper(classA)
wrapperB = Wrapper(otherInstance)
print(wrapperA.forward(10))
print(wrapperB.foo())
print("docstring is maintained: %r" % wrapperA.forward.__doc__)
Using a class decorator
With a class decorator, there is no need to override __new__ which can lead to hard to debug issues if not 100% properly implemented.
However, it has a key difference: It modifies the existing class "in-place", so the original class is lost in a way. Although you could keep a reference to it in the unlikely case that you need to.
Modifying this in-place does however also mean that you don't need to replace all your usages in your application with the new "wrapper" class, making it a lot easier to implement in an existing code-base and eliminating the risk that you forget to apply the wrapper on new instances.
from functools import update_wrapper
def _wrap(func):
"""
Wraps *func* with additional code.
"""
# we define a wrapper function. This will execute all additional code
# before and after the "real" function.
def wrapped(*args, **kwargs):
print("before-call:", func, args, kwargs)
output = func(*args, **kwargs)
print("after-call:", func, args, kwargs, output)
return output
# Use "update_wrapper" to keep docstrings and other function metadata
# intact
update_wrapper(wrapped, func)
# We can now return the wrapped function
return wrapped
def wrapper(cls):
for funcname in dir(cls):
# We skip anything starting with two underscores. They are most
# likely magic methods that we don't want to wrap with the
# additional code. The conditions what exactly we want to wrap, can
# be adapted as needed.
if funcname.startswith("__"):
continue
# We now need to get a reference to that attribute and check if
# it's callable. If not it is a member variable or something else
# and we can/should skip it.
func = getattr(cls, funcname)
if not callable(func):
continue
# Now we "wrap" the function with our additional code. This is done
# in a separate function to keep __new__ somewhat clean
wrapped = _wrap(func)
# After wrapping the function we can attach that new function ont
# our `Wrapper` instance
setattr(cls, funcname, wrapped)
return cls
#wrapper
class A:
def __init__(self):
self.a = 1
def forward(self, x):
"""
docstring (to demonstrate `update_wrapper`
"""
print("original forward")
return self.a + x
def main(self, x):
return self.forward(x) + 100
#wrapper
class Demo2:
def foo(self):
print("yoinks")
classA = A()
otherInstance = Demo2()
print(classA.forward(10))
print(otherInstance.foo())
print("docstring is maintained: %r" % classA.forward.__doc__)
Using function decorators
Another alternative, which diverges largely from the original question but may still prove insightful is using individual functions wrappers.
The code still used the same wrapper function, but here functions/methods are annotated individually.
This might give more flexibility by offering the possibility to leave some methods "unwrapped", but could easily lead to the wrapping code being executed more often than anticipated as demonstrated in the main() method.
from functools import update_wrapper
def wrap(func):
"""
Wraps *func* with additional code.
"""
# we define a wrapper function. This will execute all additional code
# before and after the "real" function.
def wrapped(*args, **kwargs):
print("before-call:", func, args, kwargs)
output = func(*args, **kwargs)
print("after-call:", func, args, kwargs, output)
return output
# Use "update_wrapper" to keep docstrings and other function metadata
# intact
update_wrapper(wrapped, func)
# We can now return the wrapped function
return wrapped
class A:
def __init__(self):
self.a = 1
#wrap
def forward(self, x):
"""
docstring (to demonstrate `update_wrapper`
"""
print("original forward")
return self.a + x
#wrap # careful: will be wrapped twice!
def main(self, x):
return self.forward(x) + 100
def foo(self):
print("yoinks")
classA = A()
print(">>> forward")
print(classA.forward(10))
print("<<< forward")
print(">>> main")
print(classA.main(100))
print("<<< main")
print(">>> foo")
print(classA.foo())
print("<<< foo")
You could inherit Wrapper from A, and use super to access the parent class.
class A:
def __init__(self, child):
self.a = 1
self.child = child
def forward(self, x):
return self.a + x
def main(self, x):
return self.child.forward(x) + 100
class Wrapper(A):
def __init__(self):
super(Wrapper, self).__init__(self, self)
def forward(x):
return "whatever"
wrapperA = Wrapper()
But if you wish to use class A, just inherit A from Wrapper. Otherwise, I can't figure out whats wrong. Please don't use functions indiscriminate. Make a class you wish to use, and another one act as a parent and don't mix roles.
#...
class A(Wrapper):
def __init__(self):
super(A, self).__init__(self)
#...

How to add methods of class to a list inside the class with a decorator

I would like to update a "class-wide" list from a decorator that decorates the class' methods and adds each decorated method to that list.
This is what came to mind:
def add(meth: callable):
Spam.eggs.append(func)
return meth
class Spam:
eggs = []
#add
def meth(self):
pass
This won't work though because Spam hasn't finished defining itself when #add is reached, and thus add raises a NameError, as pointed out in the comments.
I also tried a class method:
class Spam:
eggs = []
#classmethod
def add(cls, meth: callable):
cls.eggs.append(meth)
return meth
#add
def meth(self):
pass
But this doesn't work either because when #add is reached, add is bound to the classmethod decorated instance, which is not callable.
Here is what I need this for:
I have a class with several methods that take one argument (besides self) that transform that object in such a way that these methods may be composed with one another. I want to decorate each of these in such a way that they're automatically added to a list in the class.
E.g.:
from typing import List
def transform_meth(meth: callable):
TextProcessor.transforms.add(meth)
return meth
class TextProcessor:
transforms: List[callable] = []
#transform_meth
def m1(self, text):
return text
#transform_meth
def m2(self, text):
return text
def transform(self, text):
for transform in self.transforms:
text = transform(text)
return text
I could add the methods in the list manually, but I find the decorator to be clearer since it is close to the definition of the method, and thus it is easier to remember to decorate a new method when defining it than adding it to the list manually.
Your current approach fails because when transform_meth is called, TextProcessor isn't bound to anything yet (or if it is, that object gets overwritten when the class statement completes).
The simple solution would be to define transform_meth inside the class statement, so that it could simply declare transforms as a nonlocal variable. However, that won't work because a class statement doesn't establish a new scope.
Instead, you can define a function that creates the decorator, which takes the desired list (at that point a just a name in the body of the class statement, not from any assumed scope). That function returns a closure over the list argument
so that you can append to it.
def make_decorator(lst):
# *This* will be the function bound to the name 'transform_meth'
def _(meth):
lst.append(meth)
return meth
return _
class TextProcessor:
transforms: List[callable] = []
transform_meth = make_decorator(transforms)
#transform_meth
def m1(self, text):
return text
#transform_meth
def m2(self, text):
return text
def transform(self, text):
for transform in self.transforms:
text = transform(text)
return text
del transform_meth # Not needed anymore, don't create a class attribute
Since the arg of each method is self you can append to the object instance like so:
from functools import wraps
def appender(f):
#wraps(f)
def func(*args, **kwargs):
if f not in args[0].transforms:
args[0].transforms.append(f)
return f(*args, **kwargs)
return func
class Foo(object):
def __init__(self):
self.transforms = []
#appender
def m1(self, arg1):
return arg1
#appender
def m2(self, arg1):
return arg1
def transform(self, text):
methods = [f for f in dir(self) if not f.startswith("__") and callable(getattr(self,f)) and f != 'transform']
for f in methods:
text = getattr(self,f)(text)
return text
f = Foo()
f.transform('your text here')
print(f.transforms)
Output:
[<function Foo.m1 at 0x1171e4e18>, <function Foo.m2 at 0x1171e4268>]

Defining a class inside a function to interrupt decorator execution

I'm trying to configure a decorator at run time. This is somewhat related to my earlier question: How to configure a decorator in Python
The motivation for this is that I'm trying to use the Thespian troupe code "as-is".
Is it legal to have this code here, where I've defined the class (and therefore called the decorator) inside a class method? Again, the reason for this is that I could feed the max_count argument prior to the decorator being call.
The module is calculator.calculator (yes, bad choice perhaps)
class Scheduler:
def __init__(self):
self.actor_system = None
def start(self):
self.actor_system = ActorSystem('multiprocTCPBase')
def stop(self):
self.actor_system.shutdown()
def launch(self, count, func_and_data, status_cb):
class CalcPayload:
def __init__(self, func_and_data, status_cb):
self.func_and_data = func_and_data
self.status_cb = status_cb
#troupe(max_count=count)
class Execute(ActorTypeDispatcher):
def receiveMsg_CalcPayload(self, msg, sender):
func = msg.func_and_data['func']
data = msg.func_and_data['data']
status_cb = msg.status_cb
self.send(sender, func(data, status_cb))
exec_actor = self.actor_system.createActor(Execute)
for index in range(len(func_and_data)):
calc_config = CalcPayload(func_and_data[index], status_cb)
self.actor_system.tell(exec_actor, calc_config)
for index in range(len(func_and_data)):
result = self.actor_system.listen(timeout)
self.actor_system.tell(exec_actor, ActorExitRequest())
For various reasons, I can't apply the decorator to the class when I use it. There is a brief discussion on this in the question I referenced.
While not invalid, it is generally inadvisable to define a class as a local variable inside a function, as it would make access to the class difficult outside the function.
Instead, you can define the classes outside the function, and apply the decorator function to the class when it's actually needed by calling the decorator function with the class object:
class CalcPayload:
def __init__(self, func_and_data, status_cb):
self.func_and_data = func_and_data
self.status_cb = status_cb
class Execute(ActorTypeDispatcher):
def receiveMsg_CalcPayload(self, msg, sender):
func = msg.func_and_data['func']
data = msg.func_and_data['data']
status_cb = msg.status_cb
self.send(sender, func(data, status_cb))
class Scheduler:
def __init__(self):
self.actor_system = None
def start(self):
self.actor_system = ActorSystem('multiprocTCPBase')
def stop(self):
self.actor_system.shutdown()
def launch(self, count, func_and_data, status_cb):
exec_actor = self.actor_system.createActor(troupe(max_count=count)(Execute))
for index in range(len(func_and_data)):
calc_config = CalcPayload(func_and_data[index], status_cb)
self.actor_system.tell(exec_actor, calc_config)
for index in range(len(func_and_data)):
result = self.actor_system.listen(timeout)
self.actor_system.tell(exec_actor, ActorExitRequest())
The actor_system is going to want to build instances of your class. That means it needs to be able to derive the class object- you cannot define it inside of a method.
If you really need to apply the decorator separately, you maybe could do
def launch(self, count, func_and_data, status_cb):
wrapped = troupe(max_count=count)(Executor)
exec_actor = self.actor_system.createActor(wrapped)

Python: wrapping method invocations with pre and post methods

I am instantiating a class A (which I am importing from somebody
else, so I can't modify it) into my class X.
Is there a way I can intercept or wrap calls to methods in A?
I.e., in the code below can I call
x.a.p1()
and get the output
X.pre
A.p1
X.post
Many TIA!
class A:
# in my real application, this is an imported class
# that I cannot modify
def p1(self): print 'A.p1'
class X:
def __init__(self):
self.a=A()
def pre(self): print 'X.pre'
def post(self): print 'X.post'
x=X()
x.a.p1()
Here is the solution I and my colleagues came up with:
from types import MethodType
class PrePostCaller:
def __init__(self, other):
self.other = other
def pre(self): print 'pre'
def post(self): print 'post'
def __getattr__(self, name):
if hasattr(self.other, name):
func = getattr(self.other, name)
return lambda *args, **kwargs: self._wrap(func, args, kwargs)
raise AttributeError(name)
def _wrap(self, func, args, kwargs):
self.pre()
if type(func) == MethodType:
result = func( *args, **kwargs)
else:
result = func(self.other, *args, **kwargs)
self.post()
return result
#Examples of use
class Foo:
def stuff(self):
print 'stuff'
a = PrePostCaller(Foo())
a.stuff()
a = PrePostCaller([1,2,3])
print a.count()
Gives:
pre
stuff
post
pre
post
0
So when creating an instance of your object, wrap it with the PrePostCaller object. After that you continue using the object as if it was an instance of the wrapped object. With this solution you can do the wrapping on a per instance basis.
You could just modify the A instance and replace the p1 function with a wrapper function:
def wrapped(pre, post, f):
def wrapper(*args, **kwargs):
pre()
retval = f(*args, **kwargs)
post()
return retval
return wrapper
class Y:
def __init__(self):
self.a=A()
self.a.p1 = wrapped(self.pre, self.post, self.a.p1)
def pre(self): print 'X.pre'
def post(self): print 'X.post'
The no-whistles-or-bells solution would be to write a wrapper class for class A that does just that.
As others have mentioned, the wrapper/decorator solution is probably be the easiest one. I don't recommend modifyng the wrapped class itself, for the same reasons that you point out.
If you have many external classes you can write a code generator to generate the wrapper classes for you. Since you are doing this in Python you can probably even implement the generator as a part of the program, generating the wrappers at startup, or something.
I've just recently read about decorators in python, I'm not understanding them yet but it seems to me that they can be a solution to your problem. see Bruce Eckel intro to decorators at:
http://www.artima.com/weblogs/viewpost.jsp?thread=240808
He has a few more posts on that topic there.
Edit: Three days later I stumble upon this article, which shows how to do a similar task without decorators, what's the problems with it and then introduces decorators and develop a quite full solution:
http://wordaligned.org/articles/echo
Here's what I've received from Steven D'Aprano on comp.lang.python.
# Define two decorator factories.
def precall(pre):
def decorator(f):
def newf(*args, **kwargs):
pre()
return f(*args, **kwargs)
return newf
return decorator
def postcall(post):
def decorator(f):
def newf(*args, **kwargs):
x = f(*args, **kwargs)
post()
return x
return newf
return decorator
Now you can monkey patch class A if you want. It's probably not a great
idea to do this in production code, as it will effect class A everywhere.
[this is ok for my application, as it is basically a protocol converter and there's exactly one instance of each class being processed.]
class A:
# in my real application, this is an imported class
# that I cannot modify
def p1(self): print 'A.p1'
class X:
def __init__(self):
self.a=A()
A.p1 = precall(self.pre)(postcall(self.post)(A.p1))
def pre(self): print 'X.pre'
def post(self): print 'X.post'
x=X()
x.a.p1()
Gives the desired result.
X.pre
A.p1
X.post

Categories

Resources