#!/usr/bin/python
from functools import wraps
def logged(func):
#wraps(func)
def with_logging(*args, **kwargs):
print func.__name__ + " was called"
return func(*args, **kwargs)
return with_logging
#logged
def f(x):
"""does some math"""
return x + x * x
I want to know if wraps has the undecorated reference to the function f? I don't see it when I tried dir(f)
Modified version
#!/usr/bin/python
from functools import wraps
def logged(func):
#wraps(func)
def with_logging(*args, **kwargs):
print func.__name__ + " was called"
return func(*args, **kwargs)
with_logging.undecorated = func
return with_logging
#logged
def f(x):
"""does some math"""
return x + x * x
f.undecorated
No attribute? I was merely following what I used to do with decorator...
There is a reference to the original f in there, but it's messy to get to.
>>> f
<function f at 0x023F6DF0>
>>> f.func_closure[0].cell_contents
<function f at 0x023F6E30>
The first is the wrapped f, the second is the original f, notice the hex addresses are different.
If you need access to the original f, I suggest you wrap it differently:
def f(x):
"""does some math"""
return x + x * x
original_f = f
f = logged(f)
Now you have f and original_f, both usable.
You can access the original function by calling:
f.__wrapped__()
where f is the function that you are decorating
Related
This question already has an answer here:
python decorator arguments with # syntax
(1 answer)
Closed 2 years ago.
Following is a useless example to show that I do not manage to decorate my add function with my_decorator, giving some extra arguments:
def my_decorator(fn=None, message=None, **options):
assert fn is not None, 'fn is not found'
print('function is found')
print(message)
return fn
# This syntax works (but does not make sense...)
#my_decorator
def multiply(a, b):
return a * b
# This syntax does not work
#my_decorator(message='surprise')
def add(a, b):
return a + b
# This no sugar-syntax works
add = my_decorator(add, message='surprise') # Works
In the second example, fn is seen as None, and that raise the AssertionError, but works with "no-sugar" syntax!
What is wrong in my code? (Python 2.7. Yes, we will migrate it ...)
First, the minimum requirement to create a decorator is to implement a function (the decorator) which take a function in parameter.
For instance, you can write:
def my_decorator(f):
def wrapper(*args, **kwargs):
print("f() is called")
return f(*args, **kwargs)
return wrapper
# You can use your decorator w/o parameter:
#my_decorator
def multiply(a, b):
return a * b
You get:
multiply(5, 8)
f() is called
40
As you can see, it works but your decorator take no parameter.
So you need to create a decorator factory:
def my_decorator_factory(message):
def my_decorator(f):
def wrapper(*args, **kwargs):
print("f() is called, saying: " + message)
return f(*args, **kwargs)
return wrapper
return my_decorator
# You can use your decorator factory with a parameter:
#my_decorator_factory(message="hi")
def multiply(a, b):
return a * b
You get:
multiply(5, 8)
f() is called, saying: hi
40
But, you want the ability to call you decorator factory like a decorator (w/o parameter).
So you need to change the signature of your decorator factory to allow the calls like:
my_decorator_factory(message="something") # or
my_decorator_factory(function)
here, you need to check that the first parameter is a function of something else:
import inspect
def my_decorator_factory(message=None):
if inspect.isfunction(message):
return my_decorator_factory(message=None)(message)
else:
def my_decorator(f):
def wrapper(*args, **kwargs):
print("f() is called, saying: " + (message or "(empty)"))
return f(*args, **kwargs)
return wrapper
return my_decorator
#my_decorator_factory
def multiply(a, b):
return a * b
#my_decorator_factory(message='surprise')
def add(a, b):
return a + b
You get:
multiply(5, 8)
f() is called, saying: (empty)
40
add(3, 2)
f() is called, saying: surprise
5
You may consider using Wrapt to implement "good" decorators/decorator factories.
my_decorator(message='surprise') returns None because it uses fn=None implicitly.
You then try to apply None to add, which as you noticed cannot work.
You need a parametrized function that returns a decorator. Informally, this is called a parametrized decorator.
Here's an example:
def make_decorator(message):
def decorator(func):
def new_func(*args, **kwargs):
print(message)
return func(*args, **kwargs)
return new_func
return decorator
#make_decorator(message='surprise')
def add(a, b):
return a + b
x = add(1, 2) # prints 'surprise' and sets x to 3
Note that
#make_decorator(message='surprise')
def add(a, b):
return a + b
is equivalent to
def add(a, b):
return a + b
decorator = make_decorator(message='surprise')
add = decorator(add)
This code skips the code in the number function:
def some_decorator(x):
def wrapper(x):
return x+1
return wrapper
#some_decorator
def number(x):
x = x + 100
return x
Output:
>>> number(3)
4
I am trying to make the output of number(3) to be 104, what is wrong with the code?
Decorators pass the function as an argument to the decorator. It's your job to call the function if you want it to execute:
def some_decorator(fn):
def wrapper(x):
return fn(x) + 1 # call the function and add 1
return wrapper
#some_decorator
def n(x):
x = x + 100
return x
n(3) # 104
As a side note, it's often useful to use functools.wraps to make the wrapped function behave as expected:
from functools import wraps
def some_decorator(fn):
#wraps(fn)
def wrapper(x):
return fn(x) + 1
return wrapper
#some_decorator
def n(x):
x = x + 100
return x
print(n.__name__) # now prints `n` instead of `wrapper`
I'm trying to use a decorator to give a function access to a particular variable whenever it's called. For instance,
def make_x_available(fn):
def decorated():
x = 5
return fn()
return decorated
#make_x_available
def print_x():
print(x)
5 here stands for an arbitrary expression to be evaluated each time the function fn is called, and to be bound to the name x.
Instead of print_x walking up the stack to find x's value of 5 as I expected, it raises NameError: name 'x' is not defined.
How would I go about achieving this? And why is this error caused?
You could add x as an argument to the function:
def make_x_available(fn):
def decorated():
x = 5
return fn(x)
return decorated
#make_x_available
def print_x(x):
print(x)
print_x()
Output
5
Alternatively, you can use keyword arguments:
def make_x_available(fn):
def decorated(*args, **kwargs):
kwargs['x'] = 5
return fn(*args, **kwargs)
return decorated
#make_x_available
def print_x(*args, **kwargs):
print(kwargs['x'])
print_x()
Output
5
Not sure what it could be used for, but this would work:
from functools import wraps
def make_x_available(fn):
#wraps(fn)
def decorated(*args, **kwargs):
decorated.x = 5
return fn(*args, **kwargs)
return decorated
#make_x_available
def print_x():
print(print_x.x)
print_x()
prints:
5
Note: When you write a decorator, always use functools.wraps to keep the name, docstring, and other attributes of the decorated function.
How do I override the string representation for a single function in Python?
What I have tried:
>>> def f(): pass
...
>>> f
<function f at 0x7f7459227758>
>>> f.__str__ = lambda self: 'qwerty'
>>> f
<function f at 0x7f7459227758>
>>> f.__repr__ = lambda self: 'asdfgh'
>>> f
<function f at 0x7f7459227758>
>>> f.__str__(f)
'qwerty'
>>> f.__repr__(f)
'asdfgh'
I know I can get the expected behavior by making a class with __call__ (to make it look like a function) and __str__ (to customize the string representation). Still, I'm curious if I can get something similar with regular functions.
You can't. __str__ and __repr__ are special methods and thus are always looked up on the type, not the instance. You'd have to override type(f).__repr__ here, but that then would apply to all functions.
Your only realistic option then is to use a wrapper object with a __call__ method:
def FunctionWrapper(object):
def __init__(self, callable):
self._callable = callable
def __call__(self, *args, **kwargs):
return self._callable(*args, **kwargs)
def __repr__(self):
return '<custom representation for {}>'.format(self._callable.__name__)
As explained by MartijnPieters, you can't do it without going through a class. But you can easily write a decorator which completely hides the added complexity:
from functools import update_wrapper
class _CustomReprFunc(object):
def __init__(self, fn, repr_):
self.fn = fn
self.repr = repr_
update_wrapper(self, fn)
def __repr__(self):
return self.repr
def __call__(self, *args, **kwargs):
return self.fn(*args, **kwargs)
def custom_repr(repr_):
def decorator(fn):
return _CustomReprFunc(fn, repr_)
return decorator
Usage:
#custom_repr('foobar')
def foo():
"""foo function"""
return 'bar'
How can I wrap a recursive function, recursive calls included? For example, given foo and wrap:
def foo(x):
return foo(x - 1) if x > 0 else 1
def wrap(f):
def wrapped(*args, **kwargs):
print "f was called"
return f(*args, **kwargs)
return wrapped
wrap(foo)(x) will only output "f was called" with the first call. Recursive calls still address foo().
I don't mind monkey patching, or poking around internals. I'm not planning to add this code to the next nuclear warhead handling program, so even if it's a bad idea, I'd like to achieve the effect.
Edit: for example, would patching foo.func_globals to override foo.__name__ work? If it always does, any side-effects I should be minding?
It works if you use your wrapper function as a decorator.
def wrap(f):
def wrapped(*args, **kwargs):
print "f was called"
return f(*args, **kwargs)
return wrapped
#wrap
def foo(x):
return foo(x - 1) if x > 0 else 1
Reason being that in your example, you're only calling the result of the wrap function once. If you use it as a decorator it actually replaces the definition of foo in the module namespace with the decorated function, so its internal call resolves to the wrapped version.
Wrap the function with a class, rather than a function:
>>> def foo(x):
... return foo(x-1) if x > 0 else 1
...
>>> class Wrap(object):
... def __init__(self, f): self.f = f
... def __call__(self, *args, **kwargs):
... print "f called"
... return self.f(*args, **kwargs)
...
>>> foo = Wrap(foo)
>>> foo(4)
f called
1