I wanted to use a decorator to handle exceptions in my PyQt5 application:
def handle_exceptions(func):
def func_wrapper(*args, **kwargs):
try:
print(args)
return func(*args, **kwargs)
except Exception as e:
print(e)
return None
return func_wrapper
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
loadUi("main_window.ui",self)
self.connect_signals()
def connect_signals(self):
self.menu_action.triggered.connect(self.fun)
#handle_exceptions
def fun(self):
print("hello there!")
When I run I get the following exception:
fun() takes 1 positional argument but 2 were given
The output is False (printed args in the decorator).
The interesting thing is that when I run the fun() function directly by self.fun() in the constructor or comment the decorator, everything works. Seems like the decorator adds an additional argument, but only when the function is called by the signal. What is going on?
The decorator isn't the problem; you simply haven't defined fun with the correct number of parameters.
#handle_exceptions
def fun(self, foo):
print("hello there!")
fun is an ordinary function; self.fun is a bound method that, when called, calls fun with self as the first argument and passing its own arguments as additional arguments to fun. Whatever is calling self.fun is passing an additional argument, so the definition of fun has to be accept that.
The issue is that QAction.triggered emits a boolean when emitted. When a slot receives a signal, the arguments of the signal are submitted in the signature of the slot. When the slot has a shorter signature than the signal the extra arguments are ignored. In your case, the non-decorated function has no input parameters besides self, so the checked argument of QAction.triggered is ignored when the non-decorated function receives the signal. However, the decorated function receives an arbitrary number of arguments, so when the decorated function receives the triggered signal, the checked argument is not ignored which is the extra argument that Python is complaining about.
The problem is caused because the triggered signal is overload, that is to say it has 2 signatures:
void QAction::triggered(bool checked = false)
QAction.triggered()
QAction.triggered(bool checked)
So by default it sends a boolean(false) that clearly does not accept the "fun" method causing the error.
In this case the solution is to use the #pyqtSlot() decorator to indicate the signature that you must accept:
#pyqtSlot()
#handle_exceptions
def fun(self):
print("hello there!")
It's not the decorator which adds this argument, it is the fact that you are dealing with a method.
It would act the same way if you omitted the #handle_exceptions.
What happens?
You take self.fun and pass it to self.menu_action.triggered.connect().
Whenever a menu action triggers, it (presumably) tries to call the given callable with one argument (maybe an event argument?)
But: When you take this self.fun, you don't get the function object itself, but you get what MainWindow.fun.__get__(self) (or alike, I don't remember the exact syntax) gives you, a so-called "bound method object". It is a wrapper object which can be called and deflects the calls to the original function object, but prepends the given self to the argument list.
This leads to the fact that this object being called with one argument (the event object?) results in the original function being called with two arguments (self and the event object). As it is not ready to take this additional event object, you get the said error.
Related
Sample Code:
class Mysterious:
def fake_staic():
print('fake staic')
def fake_sta_parameters(self):
print(f"fake_sta_parameters {self}")
Mysterious.fake_sta_parameters("fbc")
# output fake_sta_parameters fbc
Mysterious.fake_staic()
# output fake staic
Please define code as follows. Why can it be executed normally? Please help me explain, thanks
def fake_staic():
print('fake staic')
this function does not have self as argument and is call with class name, hence it will be treated as class method, or static method.
Whereas in second function,
def fake_sta_parameters(self):
print(f"fake_sta_parameters {self}")
you are providing it self argument, but calling it using class, and passing a value as its argument, hence self here is treated as argument and replaced with "fbc".
If you try calling this fake_sta_parameters() using object of your class, then it will give insufficient arguments error, as when a function called using object of the class, will always consider/expect function's first argument to be self reference, and hence above function will throw error as it wont have sufficient arguments to receive 'fbc' value.
My question is related to my previous one from this topic: Decorator adds an unexpected argument
The problem is that if I create more methods with the same set of decorator, every signal calls the same one, what is kind of weird. Example below:
signal_1.connect(fun_1)
signal_2.connect(fun_2)
#pyqtSlot()
#handle_exceptions
def fun_1():
print('aaaaa')
#pyqtSlot()
#handle_exceptions
def fun_2():
print('bbbbb')
signal_1.emit()
signal_2.emit()
Output:
aaaaa
aaaaa
You have to use #functools.wraps (for more information about the need read the answer of What does functools.wraps do?)
import functools
def handle_exceptions(func):
#functools.wraps(func)
def func_wrapper(*args, **kwargs):
try:
print(args)
return func(*args, **kwargs)
except Exception as e:
print(e)
return None
return func_wrapper
When the decorator is created, it receives the function as parameter, and PyQt uses the name of that function as a "pointer" to the actual function that will be used for the slot, unless a specific name is given, then it actually returns a "wrapper" to that function.
Since you're using the slot decorator against the same wrapper (func_wrapper), PyQt "thinks" they are the same function, and overwrites the previous one (or ignores the next, I'm not sure: I've got different results, as sometimes the first function is called, others the second one).
This is related to what eyllanesc explains in his answer, which I believe is the more correct approach.
Nonetheless, an alternate solution could be to set unique names as arguments for the slot:
#pyqtSlot(name='fun_1')
#handle_exceptions
def fun_1():
print('aaaaa')
#pyqtSlot(name='fun_2')
#handle_exceptions
def fun_2():
print('bbbbb')
I built a little decorator for logging purposes.
def func_detail(func):
def func_wrapper(*args,**kwargs):
log(func.__name__+' ARGS: {}'.format(str(args)))
return func(*args,**kwargs)
return func_wrapper
This works for both object methods and normal methods. I want to use it in multithreading. I have a class which contains pid as an object attribute. Is it possible to change the decorator to log pid if it detects that the method belongs to some class and this class contains attribute pid?
I've tried:
def func_detail(func):
def func_wrapper(*args,**kwargs):
log('PID: '+self.pid if self.pid is not None else ' '+func.__name__+' ARGS: {}'.format(str(args)))
return func(*args,**kwargs)
return func_wrapper
But this not works at all. Could you help me?
ABSTRACT:
I want to be able to call attribute pid from the class where the method (func) belongs without passing self as an argument to the wrapper because in that case it would not works for methods which aren't inside a classes.
The self argument to methods is not magically made available to your func_wrapper function in your decorator. Rather, it will be the first of the position arguments you're capturing with *args. If you want to make use of it, you'll need to examine args[0] and see if it has a pid attribute.
Try this, which checks first that a first argument exists, then that if it has a pid attribute:
log('{}FUNC: {} ARGS: {}'.format('PID: {} '.format(args[0].pid)
if args and hasattr(args[0], "pid") else '',
func.__name__, args))
If you are calling a method, the object itself will be the first parameter, the self in the method implementation.
If your decorator was only applied to methods (defined as def methodName(self, ...):) you could capture the first parameter in self.
Then you could try to print self.pid and catch the exception if there isn't any such attribute.
Since there is little distinction between free functions and method, I think that you should define two decorators one for free function, and another for method, or define a decorator taking a parameter saying wether it is a method or not.
Another solution is to check if the args isn't empty and print args[0].pid if it exists.
I have a simple exception-logging decorator, which is handy for sending myself emails when my scripts throw exceptions.
def logExceptions(func):
def wrapper():
try:
func()
except Exception, e:
logger.exception(e)
return wrapper
However, if I want to decorate a class method, I have to modify wrapper() to take a 'self', otherwise I get the following error:
TypeError: wrapper() takes no arguments (1 given)
Of course, at that point I can't use it to decorate any non-class methods, because then this error occurs:
TypeError: wrapper() takes exactly 1 argument (0 given)
Is there a clean way to tackle this problem? Thank you =)
The usual thing is to define your wrapper so it accepts *args and **kwargs and passes them on to the function it wraps. This way it can wrap any function.
Also, I get the impression that what you are calling a "class method" is what Python calls an "instance method", and what you call "non-class method" is what Python calls a "function". A "non-class method" (e.g., instance method) in Python takes a self argument.
Difference between instance method, classmethod and staticmethod
First a note: both static method and class method are also functions, so standard function rules mostly apply to them. I understand your question is about the difference between static method (which has no extra arguments passed) and class method (which receives class in the first argument):
class Test(object):
def standard_method(*args, **kwargs):
# it is instance method (first argument will be instance)
return args, kwargs
#classmethod
def class_method(*args, **kwargs):
# it is class method (first argument will be class)
return args, kwargs
#staticmethod
def static_method(*args, **kwargs):
# it is static method (receives only arguments passed explicitly)
return args, kwargs
The proof (or rather self-explanatory example) is here:
>>> t = Test()
>>> t.standard_method()
((<__main__.Test object at 0x0000000002B47CC0>,), {})
>>> t.class_method()
((<class '__main__.Test'>,), {})
>>> t.static_method()
((), {})
As you see, the list of arguments passed differs depending on which type of method you choose. The problem you are facing is variable number of arguments.
Solution
There is a solution for that - use argument unpacking:
def some_decorator(func):
def wrapper(*args, **kwargs):
# do something here
# args is a tuple with positional args, kwargs is dict with keyword args
return func(*args, **kwargs)
return wrapper
After that, function returned by some_decorator will accept the same amount of arguments as decorated function.
So both these examples will work:
#some_decorator
def say_hello():
print 'hello'
#some_decorator
def say_something(something):
print something
Appendix
To give you fully complete example, it would be good if you would use such constructions (note usage of functools.wraps):
from functools import wraps
def some_decorator(func):
#wraps(func)
def wrapper(*args, **kwargs):
# do something here
# args is a tuple with positional args, kwargs is dict with keyword args
return func(*args, **kwargs)
return wrapper
The reason for that is listed in documentation for functools.wraps(): it preserves function name and docstring, effectively resulting in the wrapper looking like a wrapped function (which is useful sometimes).
An alternative to decorating is use sys.excepthook which is a callback that operates on all uncaught exceptions to which you can assign your custom logging function. The benefit is that then you dont need to mutilate (and more importantly, keep track of) every function that you are interested in logging exceptions for.
Example code:
# -*- coding: utf-8 -*-
from functools import wraps
class MyClass(object):
def __init__(self):
pass
#decorator inside class
def call(f):
#wraps(f)
def wrapper(*args):
print 'Wrapper: ', args
return wrapper
#decorated 'method' without self
#call
def myfunc(a):
pass
c = MyClass()
c.myfunc(1)
Returns:
Wrapper: (<test3.MyClass object at 0xb788a34c>, 1)
Is this normal? Can someone explain?
If this is a feature I would use it in my library.
This is perfectly normal.
The function myfunc is replacecd by an instance of wrapper. The signature of wrapper is (*args). because it is a bound method, the first argument is the instance of MyClass which is printed out after the string `Wrapper: '.
What's confusing you?
It's worth noting that if you use call as a decorator from outside of MyClass, it will generate a TypeError. One way around this is to apply the staticmethod decorator to it but then you can't call it during class construction.
It's a little bit hacky but I address how to have it both ways here.
update after comment
it gets the instance as the first argument regardless of if you type self in the parameter list because after the class is created, and an instance instantiated, it is a bound method. when you call it in the form
#instance.call
def foo(bar):
return bar + 1
it expands to
def foo(bar):
return bar + 1
foo = instance.call(f)
but note that you are calling it on an instance! This will automatically expand to a call of the form
def foo(bar):
return bar + 1
foo = MyClass.call(instance, f)
This is how methods work. But you only defined call to take one argument so this raises a TypeError.
As for calling it during class construction, it works fine. but the function that it returns gets passed an instance of MyClass when it is called for the same reason that I explained above. Specifically, whatever arguments you explicity pass to it come after the implicit and automatic placement of the instance that it is called upon at the front of the argument list.
#call
def myfunc(a):
...
is equivalent to
def myfunc(a):
...
myfunc=call(myfunc)
The orginial myfunc may have expected only one argument, a, but after being decorated with call, the new myfunc can take any number of positional arguments, and they will all be put in args.
Notice also that
def call(f)
never calls f. So the fact that
def myfunc(a)
lacks the normal self argument is not an issue. It just never comes up.
When you call c.myfunc(1), wrapper(*args) gets called.
What is args? Well, since c.myfunc is a method call, c is sent as the first argument, followed by any subsequent arguments. In this case, the subsequent argument is 1. Both arguments are sent to wrapper, so args is the 2-tuple (c,1).
Thus, you get
Wrapper: (<test3.MyClass object at 0xb788a34c>, 1)