I have to decorate a inherited method, but it decorates all inherited methods. Basically I have to create a decorator that will decorate just one method from the class.
The test looks like this
#my_decorator
class TestClass(Subclass):
pass
t = TestClass()
t.say_hi
Let's say my SubClass looks like this
class SubClass():
def __init__(self):
pass
def say_hi():
print("Hi")
def say_wow():
print("wow")
Now I have to make my_decorator, that has to decorate inherited function say_hi() to print("*****") before it prints "Hi"
I tried doing it like this, but than the decorator applies to all methods from SubClass
def my_decorator(cls)
def say_hi():
print("*********")
cls.say_hi()
return say_hi()
Naturally It applies to every function of the subclass, but how do I make it to apply to only a say_hi() function? -It also returns an TypeError "NoneType" object is not callable
First let us fix SubClass, because instance methods require an explicit instance parameter at definition time:
class SubClass():
def __init__(self):
pass
def say_hi(self):
print("Hi")
def say_wow(self):
print("wow")
Now you want the decorator to replace the say_hi method with a method that prints '****' before calling the original method. Le us write a decorator that just does that(*):
def my_decorator(cls):
orig = cls.say_hi # save the original method
def say_hi(self): # define a new one
print('****')
return orig(self) # ... calling the original method
cls.say_hi = say_hi # replace the method in the class
return cls
You can then use:
#my_decorator
class TestClass(SubClass):
pass
t = TestClass()
t.say_hi()
and get as expected:
****
Hi
(*) this is a very simple decorator that can only replace a say_hi(self) method: neither a different name, nor additional parameters, but decorators can be much smarter...
If you want to decorate a method, then decorate the method, not the class that contains it. If you want a new class, then the decorator applied to the class needs to return a class.
def print_banner(f):
def _(*args, **kwargs):
print("****")
f(*args, **kwargs)
return _
class SubClass():
def __init__(self):
pass
#print_banner
def say_hi(self, ):
print("Hi")
def say_wow(self):
print("wow")
Related
In my undertstanding, decorator class should contain __call__ or __new__ method. But cached_property in cpython repo doesn't follow the rules. Can anyone explain it for me?
class cached_property:
def __init__(self, func):
xxx
def __set_name__(self, owner, name):
xxx
def __get__(self, instance, owner=None):
xxx
__class_getitem__ = classmethod(GenericAlias)
Do all decorator classes need __call__?
decorator class should contain __call__ or __new__ method
Not all decorator classes need to implement __call__.
It's only required when we want to call the decorated object with ().
A decorator class that takes a callable to produce a callable has to implement __call__.
In this example, __call__ is implemented because we want to do data.calculate().
# Decorator to call and cache the function immediately
class PreCompute:
def __init__(self, func):
self.value = func()
def __call__(self, *args, **kwds):
return self.value
class Data:
#PreCompute
def calculate():
print("Data.calculate called")
return 42
data = Data()
# This actually calls PreCompute's __call__
print(data.calculate())
The definition of class Data here is roughly desugared to something like this,
so when calling data.calculate() we're actually calling the __call__ function from class PreCompute.
class Data:
def calculate():
print("Data.calculate called")
return 42
calculate = PreCompute(calculate)
A decorator class that takes a callable but does not produce a callable does not have to implement __call__.
For example, we can modify the class Precompute decorator to the following code, which allows us to access data.calculate as if it's an attribute.
For more information about what __get__ does, see Descriptor HowTo Guide from Python docs.
class PreCompute:
def __init__(self, func):
self.value = func()
def __get__(self, instance, owner):
return self.value
class Data:
#PreCompute
def calculate():
print("Data.calculate called")
return 42
data = Data()
# Access .calculate like an attribute
print(data.calculate)
What about __new__?
I'm not sure how OP got the impression that decorator classes must define either __call__ or __new__. I've seen __new__ being defined for use cases like #singleton decorator for classes, but as discussed in the previous section about __call__, this is also not strictly required. The only function we must define is an __init__ that receives the object to be decorated.
How does #functools.cached_property work, then?
Now going back to the question, notice from the documentation of #functools.cached_property that
it "transform a method of a class into a property", which is to be accessed without the parentheses ().
Therefore, class cached_property implements __get__ but not __call__, which is similar to the second example above.
I have a class that I cannot change (it comes from a library), which may look like
class Test(object):
def __init__(self):
pass
def bar(self, x):
return x
And I want to add a decorator to the bar method in Test, like the following, for instance:
from functools import wraps
def some_decorator(fn):
#wraps(fn)
def wrapped(*args, **kwargs):
return "<b>" + fn(*args, **kwargs) + "</b>"
return wrapped
By "adding" the decorator I mean to have a way to generate objects that have the bar method wrapped in some_decorator.
However, I cannot change the code in Test, which makes my problem harder. Is there an easy way to add a decorator in a method from a class that I cannot change in python?
As you probably know the decorator is just a function which takes a function as an argument and returns a new function, thus you can do following nasty-hacky monkeypatching:
import your_library
your_library.Test.your_function = your_decorator(your_library.Test.your_function)
If you dont want to modify the original class definition then you can achieve this with with simple inheritance itself instead of using decorators..
class Test(object):
def __init__(self):
pass
def bar(self, x):
print("In base bar..")
return x
class DerivedTest(Test):
def __init__(self):
super().__init__()
def bar(self,x):
print("In derive's bar..")
super().bar(x)
Now say when you execute:
dt=DerivedTest()
dt.bar(10)
The output will be
In derive's bar..
In base bar..
You can put whatever wrapper code you were intending to before and after the super() call..
Say I have a decorator like this:
def repeat(repeat_count):
def decorator(func):
def wrapped(self):
for X in range(repeat_count):
func() # Do Function
return wrapped
return decorator
and a class like this
class SomeClass(object):
def __init__(self, do_count):
self.some_method = repeat(do_count)(self.some_method)
def some_method(self):
print("I'm Doing Something")
Because a decorator just returns a method, it's clear that this works. However it unbinds the some_method function from the class instance, so I can no longer do something like:
>>> sc = SomeClass(10)
>>> sc.some_method()
# TypeError: wrapped() missing 1 required positional argument: 'self'
I get an exception because self is no longer automatically passed. To make this work I can simply do this:
sc.some_method(sc)
but I'd much rather not. Is there any way to rebind the method to the instance, preferably without any extra imports (as TypeMethod) would be able to accomplish.
I get an exception because self is no longer automatically passed.
Actually, it is still automatically passed. You got this exception because the way you defined the decorator required it to be passed twice.
From within the runtime of wrapped, func is already bound (i.e. it has already self passed). By defining wrapped to accept one positional argument, you've required to pass in self again, which is why sc.some_method(sc) worked correctly. The self is passed twice, as required - once implicitly, and once explicitly.
The smallest fix to your code is to remove self from the signature of wrapped, because that is already passed implicitly as per the descriptor protocol in the binding of self.some_method.
def repeat(repeat_count):
def decorator(func):
def wrapped():
for X in range(repeat_count):
func() # Do Function
return wrapped
return decorator
However, this is not really the best solution. You'll want to accept *args and **kwargs so your decorator can be applied regardless of the signature of the decorated function:
def repeat(repeat_count): # <-- the "decorator maker"
def decorator(func): # <-- the decorator
def wrapped(*args, **kwargs): # <-- this will replace "func"
for X in range(repeat_count):
func(*args, **kwargs) # <-- note: pass along the arguments!
return wrapped # <-- note: no call here!
return decorator
For a rather simple case, where you don't need to access self from the decorator itself, you can simply use the following (which is pretty much what you already did, minus the wrapper function invocation, and the passing of self).
The method passed to the decorator is already bounded to self when you assign
self.some_method = repeat(do_count)(self.some_method)
The full code:
def repeat(repeat_count):
def decorator(func):
def wrapped():
for X in range(repeat_count):
func()
return wrapped
return decorator
class SomeClass(object):
def __init__(self, do_count):
self.a = 3
self.some_method = repeat(do_count)(self.some_method)
def some_method(self): print("Accesing my a from inside: %d" % self.a)
sc = SomeClass(5)
sc.some_method()
output:
Accesing my a from inside: 3
Accesing my a from inside: 3
Accesing my a from inside: 3
Accesing my a from inside: 3
Accesing my a from inside: 3
For better encapsulation, I want to decorate instance methods with methods inside the same class.
class SomeClass(object):
#staticmethod
def some_decorator(func):
def wrapped(self):
print 'hello'
return func(self)
return wrapped
#some_decorator
def do(self):
print 'world'
x = SomeClass()
x.do()
However, this piece of code raises TypeError: 'staticmethod' object is not callable
Now I make a workaround by defining a class and overload its new method to simulate a function, but it's eventually a class, not a function.
So can I access my functions inside the class scope?
Just get rid of that #staticmethod line. You want some_decorator to behave like a plain function, not like some kind of method.
The decorator is called when the class definition is being executed, before the class object itself exists. The normal method definitions inside a class are actually just plain old functions, they become methods dynamically each time they are called as attributes of the class instance (which turns them into bound methods). But while the class object itself is being built you can treat them as plain functions.
class SomeClass(object):
def some_decorator(func):
def wrapped(self):
print 'hello'
return func(self)
return wrapped
#some_decorator
def do(self):
print 'world'
x = SomeClass()
x.do()
output
hello
world
BTW, you have an error in your decorator: it's returning wrapped() instead of wrapped.
As chepner mentions in the comments we can delete some_decorator so that it doesn't take up space in the class object after we've finished using it in the class definition. (If we accidentally try to call it we'll get an error). We could do del SomeClass.some_decorator after the class definition, but it's also perfectly valid to put a del statement inside the class definition:
class SomeClass(object):
def some_decorator(func):
def wrapped(self):
print 'hello'
return func(self)
return wrapped
#some_decorator
def do(self):
print 'world'
del some_decorator
I want a decorator that would add the decorated function to list, like this :
class My_Class(object):
def __init__(self):
self.list=[]
#decorator
def my_function(self)
print 'Hi'
I expect my_function to be added to self.list, but I just can't write this decorator. If I try to write it inside My_Class, then I would have to use #self.decorator, and self does not exist since we're outside any function. And if I try to write it out of My_Class, then I can't retrieve self from my_function.
I know quite similar questions exist, but they are overly complicated, and I'm just learning python and decorators.
You can't access self from the decorator, because the decorator is run at the time the function is defined, and at that time there are no instances of My_Class yet.
It's better to put the function list as a class attribute instead of an instance attribute. Then you can pass this list as a parameter to the decorator:
def addToList(funcList):
'''Decorator that adds the function to a given list'''
def actual_decorator(f):
funcList.append(f)
return f
return actual_decorator
class MyClass(object):
funcList = []
#addToList(funcList)
def some_function(self, name):
print 'Hello,', name
Now you can access MyClass.funcList to get the list of decorated functions.
There's nothing really special about writing decorators for bound functions (instance methods). For example, this simple example works fine:
def decorator(fn):
print "I'm decorating!"
return fn
class MyClass(object):
def __init__(self):
self.list = []
#decorator
def my_function(self):
print "Hi"
If you want to use self in your decorator, you'll treat your decorator the same as you'd treat any decorator that uses the function's args:
def decorator(fn):
def _decorator(self):
print "I'm decorating, and here's my list: %s!" % self.list
return fn(self)
return _decorator
Your list attribute should be a class attribute (and it should be renamed, because list is a builtin type). Then you can do something like this:
my_methods = []
def my_method(method):
my_methods.append(method)
return method
class MyClass(object):
my_methods = my_methods
#my_method
def my_function(self):
print 'Hi'