When I decorated the bound method in Python class, I need get some info in this decorator from outer class. Is that possible?
For example:
def modifier(func):
import sys
cls_namespace = sys._getframe(1).f_locals
cls_namespace['data'] # dictonary has no key 'data'
...
return func
class Parent:
data = "Hi!"
class Child(Parent):
@modifier
def method(self):
pass
the cls_namespace
is just incomplete namespace of current class without data
field that I need to get.
Is there ways to get it in decorator?
Function decoration takes place when the class body is being executed, nothing is known about the class itself or its base classes at this time. This means that modifier
decorates the unbound function object and only when func
is actually called on an instance will it be bound.
You could return a wrapper function to replace the decorated function, it'll be bound instead and you'll have access to self
:
from functools import wraps
def modifier(func):
@wraps(func)
def wrapper(self, *args, **kwargs):
# self is an instance of the class
self.data
return func(self, *args, **kwargs)
return wrapper
The wrapper will then be called each time method
is called on an instance.
If you must have access to the base classes at class creation time, you'll have to wait until the class Child
statement has completed execution. Until Python 3.6, that's only possible from a class decorator or metaclass; each is called after the class body is created and you'll have access to (at the least) the base classes.
Using a class decorator, for example:
def modifier(cls):
# cls is the newly created class object, including class attributes
cls.data
return cls
@modifier
class Child(Parent):
def method(self):
pass
Note the location of the decorator now.
Python 3.6 adds an __init_subclass__
method that could also give you that access; the (class) method is called each time a subclass is created of the current class:
class Parent:
def __init_subclass__(cls, **kwargs):
# cls is a subclass of Parent
super().__init_subclass__(**kwargs)
cls.data
data = "Hi!"
class Child(Parent):
def method(self):
pass
This is a particularly even solution:
def wrap_class(cls):
"""Wrap a class to allow binding all decorated methods."""
for func in cls.__dict__.values():
if hasattr(func, '__wrapped__'):
func.__wrapped__.im_class = cls
return cls
Requires you to use @functools.wraps(func)
and then to decorate the class with @wraps_class
.
Decorator example:
def decorator(func):
@wraps(func)
def inner(self, *args, **kwargs):
cls = self.__class__ # bound and straight-forward
...
def extra(*args, **kwargs):
cls = func.im_class # Not bound
...
inner.extra = extra
return inner
Usage example:
@wrap_class
class Badger:
@decorator
def stoat(self, mushroom, snake):
pass
Badger().stoat()
Badger.stoat.extra()
Not that the very first arg passed to your decorator's wrapper will be an objects instance (self
). Thanks to it you can access w=everything you need.
See Raphaels response:
Related issue
def wrapped(self, *f_args, **f_kwargs):
self is the very first argument