How to Decorate a Class

How to decorate a class?

I would second the notion that you may wish to consider a subclass instead of the approach you've outlined. However, not knowing your specific scenario, YMMV :-)

What you're thinking of is a metaclass. The __new__ function in a metaclass is passed the full proposed definition of the class, which it can then rewrite before the class is created. You can, at that time, sub out the constructor for a new one.

Example:

def substitute_init(self, id, *args, **kwargs):
pass

class FooMeta(type):

def __new__(cls, name, bases, attrs):
attrs['__init__'] = substitute_init
return super(FooMeta, cls).__new__(cls, name, bases, attrs)

class Foo(object):

__metaclass__ = FooMeta

def __init__(self, value1):
pass

Replacing the constructor is perhaps a bit dramatic, but the language does provide support for this kind of deep introspection and dynamic modification.

Decorate a class in Python by defining the decorator as a class

If you want to overwrite new_method(), just do it:

class Decorator(object):
def __init__(self, arg):
self.arg = arg
def __call__(self, cls):
class Wrapped(cls):
classattr = self.arg
def new_method(self, value):
return value * 2
return Wrapped

@Decorator("decorated class")
class TestClass(object):
def new_method(self, value):
return value * 3

If you don't want to alter __init__(), you don't need to overwrite it.

Decorating a Python class with a decorator as a class

Won't be a full answer, but I think it's helpful to review the basics of a decorator. This is what decorating looks like:

@Logger
class A:
# A's code

By definition, it's equivalent to doing this:

class A
# A's code

A = Logger(A) # Logger has to be callable because...it's called

Sources often say that decorators "modify", but that's really just the intended use. Technically, all you need is A to have a definition (so a function, method, or class) and Logger to be callable. If Logger returned "Hello, World", that's what A becomes.

Okay, let's pretend we didn't decorate A for a bit and think about what it would take for Logger(A) to be "modifying." Well, A is a class, and you call a class to create instances: A(*args). Therefore, Logger(A)(*args) must also be instances of A. But Logger(A) isn't the class A, it's an instance of Logger. Luckily, you can make instances callable by defining the __call__ method in its class. Logger's __call__ method calls the class stored in its cls attribute and returns the instance.

As for parameters in a decorator, it also helps to think about what it's equivalent to. You're interested in doing this:

@Logger(x='y')
class A:
# A code

So it's equivalent to this:

class A:
# A code

A = Logger(x = 'y')(A)

Note that Logger itself is not taking A as an argument. It's taking 'y' as an argument and returning another callable that takes A as an argument. So if Logger is a class, Logger(x = 'y') would be a Logger instance. Instances of a class can also serve as decorators if the class has a __call__ method!

Python decorating class

I'm having trouble figuring out what you're trying to do. If you want to decorate a class with a decorator that takes arguments, one way to do it is like this.

2020-09-03:

Thank you Maria-Ines Carrera for pointing out that the original code doesn't actually handle classes inheriting from other classes correctly and user2357112 supports Monica for proposing a solution that does work.

# function returning a decorator, takes arguments
def message(param1, param2):
# this does the actual heavy lifting of decorating the class
# it takes the original class, modifies it in place, and returns
# the same class
def wrapper(wrapped):
the_init = wrapped.__init__

def new_init(self):
self.param1 = param1
self.param2 = param2
the_init(self)

def get_message(self):
return "message %s %s" % (self.param1, self.param2)

wrapped.__init__ = new_init
wrapped.get_message = get_message

return wrapped
return wrapper

class Pizza(object):
def __init__(self):
print "Pizza initialization"

@message("param1", "param2")
class Pizza2(Pizza):
def __init__(self):
print "Pizza2 initialization"
super(Pizza2, self).__init__()

pizza_with_message = Pizza2()

# prints "message param1 param2"
print pizza_with_message.get_message()

This prints the following:

Pizza2 initialization
Pizza initialization
message param1 param2

Is it possible to decorate classes?

Yes, of course.

A decorator is just a function taking a parameter. That parameter can be a class.

#!/usr/bin/env python3

def decorate(cls):
print(cls)
return cls

@decorate
class Foo: pass

This code will work both in python2 and python3:

$ python example.py
__main__.Foo
$ python3 example.py
<class '__main__.Foo'>

As for function decorators, a class decorator can return an arbitrary object. You probably want to return something behaving as the original class. You can also modify the class on the fly and return the class itself.

If you are planning to have parameters for the decorator I suggest you this approach, which IMHO is the most natural (biased suggestion :D).

How to decorate class or static methods

After playing around for a while, I have found a solution that looks to me better than other approaches in SO. Maybe this can be helpful to somebody.

Basically the idea is the following:

  • Detect members which are class or static methods
  • Get the function object wrapped within these methods
  • Apply the decorator to this function
  • Wrap the decorated function within a classmethod or staticmethod instance
  • Store it in the class again

The code looks like this:

def class_decorator(cls):
for name, member in vars(cls).items():
# Good old function object, just decorate it
if isinstance(member, (types.FunctionType, types.BuiltinFunctionType)):
setattr(cls, name, method_decorator(member))
continue

# Static and class methods: do the dark magic
if isinstance(member, (classmethod, staticmethod)):
inner_func = member.__func__
method_type = type(member)
decorated = method_type(method_decorator(inner_func))
setattr(cls, name, decorated)
continue

# We don't care about anything else

return cls

What does it mean to decorate a class or parameter?

When You add decorator in C# it is like adding a property to the class/method. There will be an attribute attached to it.

If You write Unit test You will meet a simple decorator TestMethod like that:

[TestMethod]
public void TestMethod1()
{
}

The framework will use the decorators to check what test methods are in the test set.

You can check on the attribute here

There is another nice to read article about Writing Custom Attributes

Decorators are not limited to the '[ ]' form of decorators. There is also a design pattern for that, that was already mentioned before by others.



Related Topics



Leave a reply



Submit