问题
I have the following Python metaclass that adds a deco_with_args
decorator to each class:
def deco_with_args(baz):
def decorator(func):
...
return func
return decorator
class Foo(type):
def __prepare__(name, bases):
return {'deco_with_args': deco_with_args}
This allows me to use the decorator like this:
class Bar(metaclass=Foo):
@deco_with_args('baz')
def some_function(self):
...
How do I make the deco_with_args
decorator behave like an @classmethod
so that I can access the Bar
class (or whatever other class) from within the decorator
function?
I have tried using @classmethod
on the deco_with_args
function with no luck.
回答1:
There are two interpretations on your question - if you need cls
to be available when the function named decorator
in your example is called (i.e. you need your decorated methods to become class methods), it suffices that itself is transformed into a classmethod:
def deco_with_args(baz):
def decorator(func):
...
return classmethod(func)
return decorator
The second one is if you need cls
to be available when deco_with_args
itself is called, when creating the decorated function itself, at class creation. The answer that is listed as accepted right now lists the straightforward problem with that: The class does not exist yet when the class body is run, so, there is no way that at the end of parsing the class body you can have methods that would have known of the class itself.
However, unlike that answer tries to imply, that is not a real deal. All you have to do is to run your decorator code (the code that needs the cls
) lazily, at the end of the class creation process. You already have a metaclass setup, so doing this is almost trivial, by just adding another callable layer around your decorator-code:
def deco_with_args(baz):
def outter_decorator(func):
def decorator(cls):
# Code that needs cls at class creation time goes here
...
return func
return decorator
outter_decorator._deco_with_args = True
return outter_decorator
class Foo(type):
def __prepare__(name, bases):
return {'deco_with_args': deco_with_args}
def __init__(cls, cls_name, bases, namespace, **kwds):
for name, method in cls.__dict__.items():
if getattr(method, '_deco_with_args', False):
cls.__dict__[name] = method(cls)
super().__init__(cls_name, bases, namespace, **kwds)
This will be run, of course, after the class body execution is complete, but before any other Python statement after the class
is run.
If your decorator would affect other elements that are executed inside the class body itself, all you need to do is to wrap those around to warrant a lazy-execution as well.
回答2:
@classmethod
does nothing useful for your decorator because it's not invoked through a class or instance. classmethod
is a descriptor, and descriptors only take effect on attribute access. In other words, it would only help if the decorator was called like @Bar.deco_with_args('baz')
.
The next problem is that the class does not exist yet at the time the decorator is executed. Python executes all of the code in the function body before creating the class. So it's impossible to access the class in deco_with_args
or decorator
.
回答3:
You can use the descriptor protocol to capture your calls to the method and add the class as parameter on the fly:
def another_classmethod(baz):
class decorator:
def __init__(self, func):
self.func = func
def __get__(self, instance, owner):
def new_call(*args, **kwargs):
print(baz, self.func(owner, *args, **kwargs))
return new_call
return decorator
class Bar():
@another_classmethod('baz')
def some_function(cls):
return f"test {cls.__name__}"
Bar.some_function()
This prints:
baz test Bar
The main "trick" here is that the protocol when calling Bar.some_function()
is to first call __get__
then __call__
on the function returned by __get__
.
Note that __get__
is also called when you just do Bar.some_function
that's what is used in decorators like @property
.
One small remark, when using classmethod you are not supposed to name your first parameter self
as it is confusing (it would make people think that the first parameter is an instance instead of a class object/type).
来源:https://stackoverflow.com/questions/55207813/python-metaclass-decorator-how-to-use-classmethod