简体   繁体   中英

In Python, is it possible to decorate both a class and non-class method with the same decorator?

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" (eg, 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM