how to use decorator in a class - python

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

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)

python-Decorator function inside Class

I have the below code which works fine if I remove self from methods
class tests:
def __init__(self):
pass
def func(self,a):
def wrapp(x):
y=x+2
return a(y)
return wrapp
#func
def func1(self,b):
return b
print (tests.func1(10))
I believe decorator function are functions that return another function. Will that not work inside class? Ignore the indentation error as I am not achievable when I paste the code here..
Please help me how I can achieve this scenario inside class..
You can just declare your decorator outside of the class. Also, when you are decorating a class method, it seems you need to pass the self variable from the wrapper to the decorated function (changed the names for more clarity):
def add_two(fn):
def wrapper(self, x):
y = x + 2
return fn(self, y)
return wrapper
class Test:
#add_two
def func1(self, b):
return b
f = Test()
f.func1(5) # returns 7
This issue here isn't the decorator at all. This issue is you are using func1 and your decorator as static methods without removing the self argument. If you remove the self arguments this will work fine.
Without staticmethod decorator
class Test:
def add_two(func=None):
def wrapper_add_two(*args, **kwargs):
return func(*args, **kwargs) + 2
return wrapper_add_two
#add_two
def func1(b):
return b
print(Test.func1(10)) #12
With staticmethod decorator
Unfortunately using them in this manner stores them as unbound static methods and you need to bind them for this to work properly.
class Test:
#staticmethod
def add_two(func):
def wrapper_add_two(*args, **kwargs):
return func.__func__(*args, **kwargs) + 2
return wrapper_add_two
#add_two.__func__
#staticmethod
def func1(b):
return b
print(Test.func1(10)) #12
Running with the staticmethod decorator and without the function binding gives you
TypeError: 'staticmethod' object is not callable

Registering methods with decorator at class level

I'd like to be able to register/return methods at a class level. The closest answer I could find was here: Auto-register class methods using decorator, except it was centered on a global register and I'm looking for something specific to the class per below.
Code:
class ExampleClass:
def get_reports(self):
# return list of all method names with #report decorator
pass
def report(self):
# decorator here
pass
#report
def report_x(self):
return
#report
def report_y(self):
return
def method_z(self):
pass
where I'd like ExampleClass.get_reports() to return ['report_x', 'report_y'].
Not all reports will be preceded with report_, so there is likely no way to just look at method names. I'm trying to figure out how to do this to apply to a variety of situations, so just looking for 'report_' does not work in this context.
You can declare a Reporter class like this and use an instance as a class property. I used the __call__ override to shorten the decorator, but you can name the function report and decorate methods as #report.report
class Reporter:
def __init__(self):
# Maintain a set of reported methods
self._reported = set()
def __call__(self, fn, *args, **kwargs):
# Register method
self._reported.add(fn.__name__)
def decorate(*args, **kwargs):
return fn(*args, **kwargs)
return decorate
class ExampleClass:
report = Reporter()
def get_reports(self):
# return list of all method names with #report decorator
return list(self.report._reported)
#report
def report_x(self):
return
#report
def report_y(self):
return
def method_z(self):
pass
This turns out to be similar to Mach_Zero's answer. So key differences, this returns the methods, not the method names, and the implementation of get_reports() is somewhat simpler with the use of __iter__.
Code:
class Reports:
def __init__(self):
self.reports = []
def __call__(self, func):
self.reports.append(func)
return func
def __iter__(self):
return iter(self.reports)
class ExampleClass:
report = Reports()
#classmethod
def get_reports(cls):
# return list of all method names with #report decorator
return list(cls.report)
#report
def report_x(self):
return
#report
def report_y(self):
return
def method_z(self):
pass
Test Code:
print(ExampleClass.get_reports())
Results:
[
<function ExampleClass.report_x at 0x000000000AF7B2F0>,
<function ExampleClass.report_y at 0x000000000AF7B378>
]

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.

Python decorator parametrized by instance attribute?

I am trying to define a python decorator (my_decorator) for a class method (f), shown below in a simplified scenario. my_decorator is parametrized by param, which depends on the class attribute (in this case level).
class my_decorator:
def __init__(self, param):
self.param = param
def __call__(self, f):
def f_decorated(instance, c):
print("decorated with param = %d" % self.param)
return f(c)
return f_decorated
class A:
def __init__(self, level):
self.level = level
#my_decorator(param=self.level) # Here is the problematic line!
def f(x):
return x
if __name__ == "__main__":
a = A(level=2)
a.f(1) # name "self" is not defined
The above code does not work, and I get a "self" is not defined error. So my question is, is there any way to achieve the goal of context-parametrized decorator?
BTW, the use case is: I am trying to achieve persistent memoization technique (described at
memoize to disk - python - persistent memoization)
The file where the cache persists to depends on the class A, specifically 'level'. For instance, I would like to persist to the file cache_%d.txt % self.level .
Chen,
Decorator are executed during the compiled time or during the import as the class body is executed during import. So, if you execute your snippet without creating an instance of that class also will throw error.
And more over that parameter self.level inside decorator doesn't make much sense to me as its an instance variable so you can directly use inside the function f(x):
Here is some more details:
Python decorator function called at compile time
As the error says, self doesn't exist at that point. That should be clear to you: self only exists as a parameter to a method, and you're not even in a method at that time. Decorators, like all class-level code are evaluated at define time.
I'm not totally sure what you want to achieve, but you could use a string along with getattr:
class my_decorator:
def __init__(self, param_name):
self.param_name = param_name
def __call__(self, f):
def f_decorated(instance, c):
param = getattr(instance, self.param_name)
print("decorated with param = %d" % param)
return f(c)
...
class A:
def __init__(self, level):
self.level = level
#my_decorator(param_name='level')
def f(x):
return x
self is a variable as any. It's only defined inside of methods, the decorator is outside. If you need attributes of a object inside an decorator, you have the possibility to access it by string-name:
class my_decorator:
def __init__(self, param):
self.param = param
def __call__(self, f):
def f_decorated(instance, c):
print("decorated with param = %d" % getattr(instance, self.param))
return f(instance, c)
return f_decorated
class A:
def __init__(self, level):
self.level = level
#my_decorator(param='level') # Here is the problematic line!
def f(self, x):
return x
if __name__ == "__main__":
a = A(level=2)
a.f(1) # name "self" is not defined

Categories