How to pass class variables to Parameterized Decorators - python

I've a decorator which takes parameters.
def decorator(abc):
def inner(func):
def wrapper(*args, **kwargs):
print(abc) # prints base class variable
# do something
return True
return wrapper
return inner
I've Child-Parent Classes as follow:-
class ABC():
abc = 1
#decorator(abc)
def func(self):
return True
class DEF(ABC):
abc = 2
obj = DEF()
print(obj.func())
The problem that I'm facing is while passing child class variable to the decorator, it still takes the base class variable.
How do I pass the child class variable to the decorator?

Your decorator is being passed a concrete value at definition time of the base class and its function. There is no attribute access whatsoever and the function does not get magically redefined and redecorated with a new value in the derived class.
It also makes little sense to pass a class attribute to a method decorator in the first place as the method has dynamic access to it via the passed instance anyway. You can do the much simpler:
def decorator(func):
def inner(self, *args, **kwargs):
print(self.__class__.abc) # actually prints the class attribute
# do something
return True
return inner
class ABC(object):
abc = 1
#decorator
def func(self):
return True
>>> a = ABC()
>>> a.func()
1
True
>>> b = DEF()
>>> b.func()
2
True

A simple way would be to considere that what you want to process in the decorator is the run time value of an attribute. Then you just pass the attribute name and use it at run time. That is enough, because a method gets its object as its first parameter:
def decorator(attr):
def inner(func):
def wrapper(*args, **kwargs):
print(getattr(args[0], attr)) # prints current attribute
# do something
return True
return wrapper
return inner
class ABC():
abc = 1
#decorator('abc')
def func(self):
return True
class DEF(ABC):
abc = 2
obj = DEF()
obj.func()
gives as expected:
2
True

One other solution could be to override the method in DEF, since according to DEF.__mro__, since python cannot find the method declared in DEF, it will go to parent class and find it there, passing in ABC's class attribute.
Output of DEF.__mro__:
(__main__.DEF, __main__.ABC, object)
A proposed solution:
class ABC(object):
abc = 1
#decorator(abc)
def func(self):
return True
class DEF(ABC):
abc = 2
#decorator(abc)
def func(self):
return True

Related

Apply decorator to class method which accesses class attribute

Is it possible to write a decorator that acts upon a class's method and uses the class's attributes? For example, I would like to add a decorator to functions that will return an error if one of the class's attributes (which is set when the user calls the function) is False.
For example, my attempt (broken code since is_active can't access MyClass's methods):
def is_active(active):
if active == False:
raise Exception("ERROR: Class is inactive")
class MyClass():
def __init__(self, active):
self.active = active
#is_active
def foo(self, variable):
print("foo")
return variable
#is_active
def bar(self, variable):
print("bar")
return variable
where the expected behaviour is:
cls = MyClass(active=True)
cls.foo(42)
---> function prints "foo" and returns 42
cls = MyClass(active=False)
cls.foo(42)
---> function raises an exception as the active flag is False
The above is a dummy example and the actual use case is more complex, but hopefully this shows the problem I'm facing.
If the above is possible, my extra question is: is it possible to "hide"/delete the methods from the instantiated class based on this flag. For example, if the user instantiates the class with a active=False then when they're using iPython and press <tab>, they can only see the methods which are permitted to be used?
Thank you.
Decorators can be confusing. Note a function is passed as a parameter and the decorator expects that a function (or callable object) is returned. So you just need to return a different function. You have everything else you need since self is passed as the first argument to a class method. You just need to add a new function in your decorator that does what you want.
def is_active_method(func):
def new_func(*args, **kwargs):
self_arg = args[0] # First argument is the self
if not self_arg.active:
raise Exception("ERROR: Class is inactive")
return func(*args, **kwargs)
return new_func
class MyClass():
def __init__(self, active):
self.active = active
#is_active_method
def foo(self, variable):
print("foo")
return variable
#is_active_method
def bar(self, variable):
print("bar")
return variable
m = MyClass(True) # Prints foo from the method
m.foo(2)
m = MyClass(False) # Outputs the exception
m.foo(2)

Override function decorator argument

I've a base class and a child class. Base class has a class variable which is passed to decorator. Now, when I inherit Base into child, and change the variable value, the decorator does not take the over-ride class variable value.
Here's the code:-
class Base():
variable = None
#decorator(variable=variable)
def function(self):
pass
class Child(Base):
variable = 1
Without overriding the function again: How do I pass child class variable to the decorator?
The comment from deceze already explained why this is not getting reflected on the sub classes.
One workaround is, you can build the logic on the decorator side.
Ie, something like this.
def decorator(_func=None, *, variable):
def decorator_func(func):
def wrapper(self, *args, **kwargs):
variable_value = getattr(self.__class__, variable)
print(variable_value)
# You can use this value to do rest of the work.
return func(self, *args, **kwargs)
return wrapper
if _func is None:
return decorator_func
else:
return decorator_func(_func)
Also update the decorator syntax from #decorator(variable=variable) to #decorator(variable='variable')
class Base:
variable = None
#decorator(variable='variable')
def function(self):
pass
DEMO
b = Base()
b.function() # This will print `None`.
Lets try with the subclass
b = Child()
b.function() # This will print `1`.

Calling different parent-class methods with one decorator

So basically my problem seems like this.
class A():
def func(self):
return 3
class B():
def func(self):
return 4
class AA(A):
def func(self):
return super(AA, self).func
class BB(B):
def func(self):
return super(BB, self).func
The func function is doing some work and one of the things it does is getting some attribute(or running method or whatever) from it's parent class.
Since func originally does the same logic at both cases (except that only parent class changes) I'd like to do this with decorators.
Is it possible? if so how to do it? Do I have somehow to pass parent-class as a argument?
I'll be very grateful for answers it's been bothering me for a while now.
There is no need to use super to access data attributes of a parent class.
Neither does a class need a parent in order for access to data attributes to work.
You can use a mixin to do the job:
# A and B stay the same - they still have a c attribute
class A():
c = 3
class B():
c = 4 # I've changed B to make it clear below
#Instead have a mixin which defines func()
class Mixin:
def func(self):
# func has its behaviour here
return self.c
class AA(Mixin, A):
pass
class BB(Mixin, B):
pass
a = AA()
b = BB()
print(a.func())
print(b.func())
Output:
3
4
You could do it with a single class decorator by defining a generic method inside of it that does what you want, and then adding it to the class being decorated. Here's what I mean:
def my_decorator(cls):
def call_super_func(self):
return super(type(self), self).func()
setattr(cls, 'call_super_func', call_super_func)
return cls
class A():
def func(self):
print('in A.func')
return 3
class B():
def func(self):
print('in B.func')
return 4
#my_decorator
class AA(A):
def func(self):
print('in AA.func')
return self.call_super_func()
#my_decorator
class BB(B):
def func(self):
print('in BB.func')
return self.call_super_func()
aa = AA()
aa.func()
bb = BB()
bb.func()
Output:
in AA.func
in A.func
in BB.func
in B.func
Of course you could eliminate the need to do this by just defining baseclass for A and B that has a call_super_func() method in it that they would then both inherit.

Python class method decorator

I write a decorator for class method
def decor(method):
def wrapped(self, *args, **kwargs):
return method(self, *args, **kwargs)
# [*]
return wrapped
I would like use this like:
class A(metaclass=mymetaclass):
#decor
def meth(self):
pass
How I can in decorator add method/variable to class which has decorated method? I need it do near [*].
Inside wrapped I could write self.__class__, but what to do here?
I cannot imagine a way to meet such a requirement, because decor function only receives a function object that knows nothing about a containing class.
The only workaround that I can imagine is to use a parameterized decorator and pass it the class being decorated
def decor(cls):
def wrapper(method):
def wrapped(self, *args, **kwargs):
return self.method(*args, **kwargs)
print method # only a function object here
return wrapped
print cls # here we get the class and can manipulate it
return wrapper
class A
#decor(A)
def method(self):
pass
Alternatively, you could decorate the class itself:
def cdecor(cls):
print 'Decorating', cls # here we get the class and can manipulate it
return cls
#cdecor
class B:
def meth(self):
pass
gives:
Decorating __main__.B
It looks like you just wanted to decorate one of a classes functions, not specifically an #classmethod. Here's a simple way that I did it when I wanted to call a classes save function when the function returned a successful result:
def save_on_success(func):
""" A decorator that calls a class object's save method when successful """
def inner(self, *args, **kwargs):
result = func(self, *args, **kwargs)
if result:
self.save()
return result
return inner
Here is an example of how it was used:
class Test:
def save(self):
print('saving')
#save_on_success
def test(self, var, result=True):
print('testing, var={}'.format(var))
return result
Testing to make sure it works as expected:
>>> x = Test()
>>> print(x.test('test True (should save)', result=True))
testing, var=test True (should save)
saving
True
>>> print(x.test('test False (should not save)', result=False))
testing, var=test False (should not save)
False
It looks like it is not directly possible, according to this response :
Get Python function's owning class from decorator
What you could do instead is providing a decorator for your class, something like that :
class InsertMethod(object):
def __init__(self, methodToInsert):
self.methodToInsert = methodToInsert
def __call__(self, classObject):
def wrapper(*args, **kwargs):
setattr(classObject, self.methodToInsert.__name__, self.methodToInsert)
return classObject(*args, **kwargs)
return wrapper
def IWillBeInserted(self):
print "Success"
#InsertMethod(IWillBeInserted)
class Something(object):
def __init__(self):
pass
def action(self):
self.IWillBeInserted()
a = Something()
a.action()
Actually, you may decorate the class itself:
def class_decorator(class_):
class_.attribute = 'value'
class_.method = decorate(class_.method)
return class_
#class_decorator
class MyClass:
def method(self):
pass
I'm a little late to the party, but late is better than never eh? :)
We can do this by decorating our class method with a decorator which is itself a class object, say B, and then hook into the moment when Python calls B.__get__ so to fetch the method. In that __get__ call, which will be passed both the owner class and the newly generated instance of that class, you can elect to either insert your method/variable into the original owner class, or into the newly defined instance.
class B(object):
def __init__(self, f):
self.f = f
def __call__(self, *args, **kwargs):
return self.f(*args, **kwargs)
def __get__(self, instance, owner):
instance.inserted = True
# owner.inserted = True
def wrapper(*args, **kwargs):
return self(instance, *args, **kwargs)
return wrapper
class A:
#B
def method(self):
pass
if __name__ == "__main__":
a = A()
a.method()
b = A()
print(hasattr(a, 'inserted'))
print(hasattr(b, 'inserted'))
In this example, we're wrapping def method(self) with #B. As written, the inserted attribute inserted will only persist in the a object because it's being applied to the instance. If we were to create a second object b as shown, the inserted attribute is not included. IE, hasattr(a, 'inserted') prints True and hasattr(b, 'inserted') prints False. If however we apply inserted to the owner class (as shown in the commented out line) instead, the inserted attribute will persist into all future A() objects. IE hasattr(a, 'inserted') prints True and hasattr(b, 'inserted') prints True, because b was created after a.method() was called.

how to use decorator in a class

I know there is similar question, but my scenario is somehow different: refer to codes:
class MyClass(object):
def __init__(self, log_location)
self.logs = logging(log_location) # create log object by the log_location, this object should be used by the decorator fucntion
def record_log(log_object):
""" this is the decorator function
"""
def deco(func):
def wrap(*args, **kwargs):
rs = func()
# use log object to record log
if rs:
log_object.record('success')
else:
log_object.record('fail')
return wrap
return deco
#record_log(self.logs)
def test(self):
rs = do_some_thing
if rs:
return True
return False
def main():
my_class = MyClass()
my_class.test()
But, there is an error like this:
#record_log(self.logs)
NameError: name 'self' is not defined
Hos should I use the instance attribute self.logs in a decorator function in such scenario like this??
Thanks very much!
You can not pass a reference to self or any attribute of self at this point. The #record_log line is executed (the method is decorated) before the code in main is executed, i.e. before any instance of MyClass is created at all -- in fact, even before the definition of MyClass has been completed! But remember that
#record_log(self.logs)
def test(self, n):
is actually just syntactic sugar for
test = record_log(self.logs)(test)
So one way to work around your problem would be to redefine test in your __init__, i.e.
def __init__(self, log_location)
self.logs = logging(log_location)
self.test = record_log(self.logs)(self.test)
Also note that your decorator is not passing any parameters to func and not returning the results. Also, it should probably be defined on module level (before the class).
def record_log(log_object):
def deco(func):
def wrap(*args, **kwargs):
rs = func(*args, **kwargs) # pass parameters
if rs:
log_object.record('success')
else:
log_object.record('fail')
return rs # return result
return wrap
return deco
There are several objections about your code:
deco() is redundant. You can directly return wrap from record_log().
If you only plan to decorate MyClass's methods, then there is no point in passing log_object to the decorator, as self.logs will always be used. Otherwise, consider moving the decorator to module level, as already suggested by others.
The decorated method's return value is currently lost.
The call to the decorated function does not pass self to it.
The proper code would therefore be:
class MyClass(object):
def __init__(self, log_location):
self.logs = logging(log_location)
def record_log(func):
""" this is the decorator function
"""
def wrap(self):
rs = func(self)
# use log object to record log
if rs:
print 1
self.logs.record('success')
else:
print 2
self.logs.record('fail')
return rs
return wrap
#record_log
def test(self):
rs = do_some_thing
if rs:
return True
return False

Categories