Why python decorator will lose func attribute __doc__? - python

def decor(fun):
def living(*args, **kw):
return fun(*args, **kw)
return living
#decor
def test():
'''function doc'''
pass
print test.__doc__
why the result is None? Something happened when I use decorator? Thanks to answer!

Because when you wrap a function in a decorator:
#decor
def test:
you get back the function created by the decorator, (living, in this case) which doesn't have the same docstring, etc. It doesn't "lose" this data, living never had it!
You can get around this with functools.wraps:
from functools import wraps
def decor(fun):
#wraps(fun)
def living(*args, **kw):
...
return func
A quick demo to prove the point:
>>> def wrapper(f):
def func(*args):
"""The wrapper func's docstring."""
return f(*args)
return func
>>> #wrapper
def test(x):
"""The test func's docstring."""
return x ** 2
>>> test.__doc__
"The wrapper func's docstring."
versus
>>> from functools import wraps
>>> def wrapper(f):
#wraps(f)
def func(*args):
"""The wrapper func's docstring."""
return f(*args)
return func
>>> #wrapper
def test(x):
"""The test func's docstring."""
return x ** 2
>>> test.__doc__
"The test func's docstring."

That's because your decorator is basically replacing your function. You need to use functools.wraps() to store the decorated functions internals like __name__ and __doc__.
You can test this easily by adding a docstring to your decorator function living():
>>> def decor(fun):
... def living(*args, **kw):
... """This is the decorator for living()"""
... return fun(*args, **kw)
... return living
...
>>> #decor
... def test():
... """function doc"""
... pass
...
>>> test.__doc__
'This is the decorator for living()'
Example from the docs of functools.wraps() which saves the wrapped functions name and docstring.
>>> from functools import wraps
>>> def my_decorator(f):
... #wraps(f)
... def wrapper(*args, **kwds):
... print 'Calling decorated function'
... return f(*args, **kwds)
... return wrapper
...
>>> #my_decorator
... def example():
... """Docstring"""
... print 'Called example function'
...
>>> example()
Calling decorated function
Called example function
>>> example.__name__
'example'
>>> example.__doc__
'Docstring'

Related

What's internal mechanism of decorator in Python

I know that the decorator is a function that takes another function and extends its behavior.
In the example below, I previously assume that test() now is effectively equivalent to decorator(test)().
def decorator(func):
def wrapper(*args, **kwargs):
...
res = func(*args, **kwargs)
...
return res
return wrapper
#decorator
def test():
pass
However, after adding a function attribute in the decorator and run both test() and decorator(test)(), the results are different. It seems that in the case of decorator(test)(), the decorator function is indeed ran so that num is reset; when using #decorator instead, the decorator function is not ran as I expected?
def decorator(func):
decorator.num = 0
def wrapper(*args, **kwargs):
...
res = func(*args, **kwargs)
...
return res
return wrapper
#decorator
def test():
pass
def test2():
pass
decorator.num = 5
test()
print(decorator.num)
decorator.num = 5
decorator(test2)()
print(decorator.num)
---------------------
5
0
Your confusion stems from when the decorator runs. The syntax
#decorator
def foo(): ...
is equivalent to
def foo(): ...
foo = decorator(foo)
That is, immediately after the function is defined, the decorator is called on it, and the result of calling the decorator is assigned back to the original function name. It's called only once, at definition time, not once per function call.
The same is true of classes. The syntax
#decorator
class Foo: ...
is equivalent to
class Foo: ...
Foo = decorator(Foo)

Create decorator for recursion count

I need to create a decorator to call Ackermann function and write down its attributes: the number of recursions and execution time.
I tried the below code but it works wrong.
Could anyone help?
def visualise(func):
visualise.level = 0
#wraps (func)
def wrapper(*args, **kwargs):
start=time.perf_counter()
visualise.level += 1
calls = func(*args, **kwargs)
visualise.level -= 1
last_time_taken=time.perf_counter()-start
setattr(func,'calls',calls)
setattr(func,'last_time_taken',last_time_taken)
print(last_time_taken)
return calls
return wrapper
#visualise
def ackermann(n, m):
...
ackermann(2, 2)
ackermann.last_time_taken
ackermann.calls
The result should be ackermann.last_time_taken = 0.00000034 seconds (for example) and ackermann.calls = 7.
Here's a minimal version of your code:
from functools import wraps
def visualise(func):
#wraps(func)
def wrapper(*args, **kwargs):
setattr(func, "bar", 42) # or func.bar = 42
return wrapper
#visualise
def foo():
...
foo()
print(foo.bar) # => AttributeError: 'function' object has no attribute 'bar'
How to debug?
We can start by making sure the attribute can really be set on a function:
from functools import wraps
def visualise(func):
setattr(func, "bar", 42)
return func
#visualise
def foo():
...
foo()
print(foo.bar) # => 42
OK, that worked. Next, let's go back to the wrapped code and make sure we're adding the property to the correct function:
from functools import wraps
def visualise(func):
#wraps(func)
def wrapper(*args, **kwargs):
print(2, func) # => 2 <function foo at 0x00000248F38EFA30>
setattr(func, "bar", 42)
print(1, wrapper) # => 1 <function foo at 0x00000248F38EFAC0>
return wrapper
#visualise
def foo():
...
foo()
print(3, foo) # => 3 <function foo at 0x00000248F38EFAC0>
print(foo.bar) # => AttributeError: 'function' object has no attribute 'bar'
There's the problem; you're adding the property to the original function, but trying to access the property on the decorated function. That's a totally different object.
Fix it:
from functools import wraps
def visualise(func):
#wraps(func)
def wrapper(*args, **kwargs):
setattr(wrapper, "bar", 42) # wrapper, not func
return wrapper
#visualise
def foo():
...
foo()
print(foo.bar) # => 42
See also Counting recursive calls of a function

Python: How can i decorate function to change it into class method

I have code like this and i want to write decorator which will add decoradted function as class method of class A.
class A:
pass
#add_class_method(A)
def foo():
return "Hello!"
#add_instance_method(A)
def bar():
return "Hello again!"
assert A.foo() == "Hello!"
assert A().bar() == "Hello again!"
What about this approach?
P.S. The code is not structurally optimized for the sake of clarity
from functools import wraps
class A:
pass
def add_class_method(cls):
def decorator(f):
#wraps(f)
def inner(_, *args, **kwargs):
return f(*args, **kwargs)
setattr(cls, inner.__name__, classmethod(inner))
return f
return decorator
def add_instance_method(cls):
def decorator(f):
#wraps(f)
def inner(_, *args, **kwargs):
return f(*args, **kwargs)
setattr(cls, inner.__name__, inner)
return f
return decorator
#add_class_method(A)
def foo():
return "Hello!"
#add_instance_method(A)
def bar():
return "Hello again!"
assert A.foo() == "Hello!"
assert A().bar() == "Hello again!"
Is this what You were going for:
class A:
def __init__(self):
pass
#classmethod
def foo(cls):
return "Hello!"
def bar(self):
return "Hello again!"
print(A.foo())
print(A().bar())
read docs here
class MyClass:
def method(self):
# instance Method
return 'instance method called', self
#classmethod
def cls_method(cls):
#Classmethod
return 'class method called', cls
#staticmethod
def static_method():
# static method
return 'static method called'
You need to instantiate MyClass To reach(Call) Instance Method
test = MyClass()
test.method()
You Can directly access class Method without instantiate
MyClass.cls_method()
MyClass.static_method()

access to the name of decorator by the decoree

I am decorating a function foo with a_decorator
#a_decorator(params)
def foo(x):
# print('Called',decorator_name)
# other magic
Is there a way to access the name a_decorator inside foo so that I can print
'Called a_decorator'
def a_decorator(some_function):
def wrapper():
some_function()
return some_val
return wrapper
It can be done by attaching decorator name to wrapper function:
from functools import wraps
def a_decorator(fn):
#wraps(fn)
def wrapper(*args, **kwargs):
val = fn(*args, **kwargs)
# some action
return val
wrapper.decorated_by = a_decorator
return wrapper
#a_decorator
def foo(x):
print(x, foo.decorated_by.__name__)
foo('test') # prints: test a_decorator
Function in python are first class and you can treat them as an object, attach attributes, etc.

Decorator for function with optional arguments

I have a function my_f with many arguments (some of them can be optional), for example,
def my_f(a, b, opt_arg = 3):
#do some stuff
Now I want to write some decorator for this function that will do some simple stuff depending on the optional argument opt_arg. I do not know the number of arguments of my_f but I know the name of the optional argument. How I can do this? Is is it possible to do in such way that both variants my_f(1,2, opt_arg=3) and my_f(opt_arg =3, 1,2,3,4) would work correct?
Use a **kw variable keywords argument in your decorator wrapper and see if opt_arg is present in it:
from functools import wraps
def yourdecorator(f):
#wraps(f)
def wrapper(*args, **kw):
opt_arg = kw.get('opt_arg')
if opt_arg is not None:
print "opt_arg was given and set to {!r}".format(opt_arg)
return f(*args, **kw)
return wrapper
Demo:
>>> from functools import wraps
>>> def yourdecorator(f):
... #wraps(f)
... def wrapper(*args, **kw):
... opt_arg = kw.get('opt_arg')
... if opt_arg is not None:
... print "opt_arg was given and set to {!r}".format(opt_arg)
... return f(*args, **kw)
... return wrapper
...
>>> #yourdecorator
... def my_f(a, b, opt_arg=3):
... pass
...
>>> my_f(42, 81)
>>> my_f(42, 81, opt_arg=3.14)
opt_arg was given and set to 3.14

Categories