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
orstaticmethod
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
Exif Manipulation Library for Python
How to Run Scrapy from Within a Python Script
How to Make a 4D Plot with Matplotlib Using Arbitrary Data
Matplotlib Axes.Plot() VS Pyplot.Plot()
I Expect 'True' But Get 'None'
How to Write to a File, Using the Logging Python Module
Get a List of All the Encodings Python Can Encode To
Use Python's String.Replace VS Re.Sub
Python Scope: "Unboundlocalerror: Local Variable 'C' Referenced Before Assignment"
How to Get the "Id" After Insert into MySQL Database with Python
Python Extending with - Using Super() Python 3 VS Python 2
How to Remove an Element in Lxml
Subprocess.Call() Arguments Ignored When Using Shell=True W/ List
Error Installing Psycopg2, Library Not Found for -Lssl
Fastest Way to Grow a Numpy Numeric Array
How to Get the Difference Between Two Dictionaries in Python