functools.wraps()
is for wrapper functions:
import contextlib
import functools
def file_reader(func):
@functools.wraps(func)
@contextlib.contextmanager
def wrapper(file, * args, ** kwargs):
close = kwargs.pop('close', True) # remove `close`
argument
if present
f = open(file)
try:
yield func(f, * args, ** kwargs)
finally:
if close:
f.close()
return wrapper
Example
@file_reader
def f(file):
print(repr(file.read(10)))
return file
with f('prog.py') as file:
print(repr(file.read(10)))
If you want to use a class-based context manager then a workaround is:
def file_reader(func):
@functools.wraps(func)
def helper( * args, ** kwds):
return File(func, * args, ** kwds)
return helper
file = f('prog.py') # use as ordinary function print(repr(file.read(20))) file.seek(0) for line in file: print(repr(line)) break file.close()
Although I don't know what the error you're seeing is from, it looks like you are probably doomed anyway:
>>> import functools
>>> def foo():
... pass
...
>>> class bar:
... pass
...
>>> functools.wraps(foo)(bar)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib64/python3.2/functools.py", line 48, in update_wrapper
setattr(wrapper, attr, value)
AttributeError: attribute '__doc__' of 'type' objects is not writable
>>> bar.__doc__
>>> bar.__doc__ = 'Yay'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: attribute '__doc__' of 'type' objects is not writable
I want to write a decorator for some functions that take file as the first argument. The decorator has to implement the context manager protocol (i.e. turn the wrapped function into a context manager), so I figured I needed to wrap the function with a class.,The code above turns the decorated function into a context manager. I want to be able to do:,I'm not really experienced with the decorator pattern and have never implemented a context manager before, but what I wrote works in Python 2.7 and it also works in Python 3.3 if I comment out the wraps line.,To make it behave identically whether the decorated function is used directly or as a context manager you should return self in __enter__():
from functools
import wraps def _file_reader(func): ""
"A decorator implementing the context manager protocol for functions that read files."
""
# @wraps(func) class CManager: def __init__(self, source, * args, ** kwargs): self.source = source self.args = args self.kwargs = kwargs self.close = kwargs.get('close', True) def __enter__(self): # _file_obj is a little helper that opens the file
for reading self.fsource = _file_obj(self.source, 'r') return func(self.fsource, * self.args, ** self.kwargs) def __exit__(self, exc_type, exc_value, traceback): if self.close: self.fsource.close() return False
return CManager
import contextlib
import functools def file_reader(func): @functools.wraps(func) @contextlib.contextmanager def wrapper(file, * args, ** kwargs): close = kwargs.pop('close', True) # remove `close`
argument
if present f = open(file) try: yield func(f, * args, ** kwargs)
finally: if close: f.close() return wrapper
Last Updated : 23 Sep, 2021,GATE CS 2021 Syllabus,GATE Live Course 2023
wrapper
A wrapper
function
wrapper
A wrapper
function
First Function
Help on
function wrapper in module __main__:
wrapper( * args, ** kwargs)
A wrapper
function
Second Function
Help on
function wrapper in module __main__:
wrapper( * args, ** kwargs)
A wrapper
function
first_function
This is docstring
for first
function
second_function
This is docstring
for second
function
First Function
Help on
function first_function in module __main__:
first_function( * args, ** kwargs)
This is docstring
for first
function
Second Function
Help on
function second_function in module __main__:
second_function( * args, ** kwargs)
This is docstring
for second
function
first_function
This is docstring
for first
function
second_function
This is docstring
for second
function
This issue tracker has been migrated to GitHub, and is currently read-only. For more information, see the GitHub FAQs in the Python's Developer Guide., This issue has been migrated to GitHub: https://github.com/python/cpython/issues/85404
# PROBLEM
When using `functools.wraps`, the signature claims one set of defaults, but the (wrapped) function uses the original (wrappee) defaults.
Why might that be the desirable default?
# PROPOSED SOLUTION
Adding '__defaults__', '__kwdefaults__' to `WRAPPER_ASSIGNMENTS` so that actual defaults can be consistent with signature defaults.
# Code Demo
```python
from functools import wraps
from inspect import signature
def g(a: float, b=10):
return a * b
def f(a: int, b=1):
return a * b
assert f(3) == 3
f = wraps(g)(f)
assert str(signature(f)) == '(a: float, b=10)' # signature says that b defaults to 10
assert f.__defaults__ == (1,) # ... but it's a lie!
assert f(3) == 3 != g(3) == 30 # ... and one that can lead to problems!
```
Why is this so? Because `functools.wraps` updates the `__signature__` (including annotations and defaults), `__annotations__`, but not `__defaults__`, which python apparently looks to in order to assign defaults.
One solution is to politely ask wraps to include these defaults.
```python
from functools import wraps, WRAPPER_ASSIGNMENTS, partial
my_wraps = partial(wraps, assigned=(list(WRAPPER_ASSIGNMENTS) + ['__defaults__', '__kwdefaults__']))
def g(a: float, b=10):
return a * b
def f(a: int, b=1):
return a * b
assert f(3) == 3
f = my_wraps(g)(f)
assert f(3) == 30 == g(3)
assert f.__defaults__ == (10,) # ... because now got g defaults!
```
Wouldn't it be better to get this out of the box?
When would I want the defaults that are actually used be different than those mentioned in the signature?
<!--
Thanks for your contribution!
Please read this comment in its entirety. It's quite important.
# Pull Request title
It should be in the following format:
```
bpo-NNNN: Summary of the changes made
```
Where: bpo-NNNN refers to the issue number in the https://bugs.python.org.
Most PRs will require an issue number. Trivial changes, like fixing a typo, do not need an issue.
# Backport Pull Request title
If this is a backport PR (PR made against branches other than `master`),
please ensure that the PR title is in the following format:
```
[X.Y] <title from the original PR> (GH-NNNN)
```
Where: [X.Y] is the branch name, e.g. [3.6].
GH-NNNN refers to the PR number from `master`.
-->
Posted to stackoverflow to gather opinions about the issue: https: //stackoverflow.com/questions/62782230/python-functools-wraps-doesnt-deal-with-defaults-correctly
Also made GitHub PR: https: //github.com/python/cpython/pull/21379
Further, note that even with the additional '__defaults__', and '__kwdefaults__', `functools.wraps`
breaks when keyword only arguments involved:
``
`
from functools import wraps, WRAPPER_ASSIGNMENTS, partial
# First, I need to add `
__defaults__` and `
__kwdefaults__` to wraps, because they don't come for free...
my_wraps = partial(wraps, assigned=(list(WRAPPER_ASSIGNMENTS) + ['__defaults__', '__kwdefaults__']))
def g(a: float, b=10):
return a * b
def f(a: int, *, b=1):
return a * b
# all is well (for now)...
assert f(3) == 3
assert g(3) == 30
`
``
This:
``
`
my_wraps(g)(f)(3)
`
``
raises TypeError(missing required positional argument 'b'), expected
Note that `wraps(g)(f)(3)`
doesn 't throw a TypeError, but the output is not consistent with the signature (inspect.signature(wraps(g)(f)) is (a: float, b=10), so 3 should be multiplied by 10). This is because __defaults__ wasn'
t updated.See
for example, that third - party from boltons.funcutils
import wraps works as expected.And so do(the very popular) wrapt and decorator packages.Boltons works
for
wraps(f)(g), but not wraps(g)(f) in my example.
See: https: //stackoverflow.com/questions/62782709/pythons-functools-wraps-breaks-when-keyword-only-arguments-involved
Is this actually a bugfix ?
Hi Terry, sorry for the later reply.
Is this a bugfix? Well, I'm not sure what you would call a bug. Can't one
always redefine a bug to be a feature, and visa versa?
I would definitely say that the behavior (seeing one default in the
signature, but a different one actually taking effect) is probably not a
good one -- as this could lead to very hard to find... bugs. It seems in
fact that third party "fix your decorators" packages such as `wrapt` and
`boltons.funcutils` agree, since their implementation of `wraps` doesn't
have this... "misaligned-by-default feature" that `functools.wraps` does.
Unless I'm missing something, my guess of why `functools.wraps` doesn't
include what I put in my pull request is that it breaks some tests. But I
had a look at the failing test and it seems that it is the test that is
"wrong" (i.e. tests for a behavior that really shouldn't be the default).
See comment:
https://github.com/python/cpython/pull/21379#issuecomment-655661983
The question is: Is there a lot of code out there that depends on this
misaligned behavior. My guess is not.
On Fri, Jul 10, 2020 at 9:58 PM Terry J. Reedy <report@bugs.python.org>
wrote:
>
> Terry J. Reedy <tjreedy@udel.edu> added the comment:
>
> Is this actually a bugfix?
>
> ----------
> nosy: +terry.reedy
> versions: +Python 3.10 -Python 3.8
>
> _______________________________________
> Python tracker <report@bugs.python.org>
> <https: //bugs.python.org/issue41232>
> _______________________________________
>
To me your examples seem like a misuse of functools.wraps.IIUC its purpose is to make a wrapper that accepts solely * args and ** kwargs appear to be the wrapped function; it doesn 't seem to be intended to be used when the wrapper takes arguments of different types or that have different default values. I can 't think of a situation where you' d use wraps with a decorator that doesn 't take just *args and **kwargs. That' s not to say there aren 't occasions where you' d want to to that, just that wraps isn 't the right tool.
Returns the same as lru_cache(maxsize=None), creating a thin wrapper around a dictionary lookup for the function arguments. Because it never needs to evict old values, this is smaller and faster than lru_cache() with a size limit.,The wrapped function is instrumented with a cache_parameters() function that returns a new dict showing the values for maxsize and typed. This is for information purposes only. Mutating the values has no effect.,To help measure the effectiveness of the cache and tune the maxsize parameter, the wrapped function is instrumented with a cache_info() function that returns a named tuple showing hits, misses, maxsize and currsize.,Since a dictionary is used to cache results, the positional and keyword arguments to the function must be hashable.
@cache
def factorial(n):
return n * factorial(n - 1) if n
else 1
>>>
factorial(10) # no previously cached result, makes 11 recursive calls
3628800
>>>
factorial(5) # just looks up cached value result
120
>>>
factorial(12) # makes two new recursive calls, the other 10 are cached
479001600
class DataSet:
def __init__(self, sequence_of_numbers):
self._data = tuple(sequence_of_numbers)
@cached_property
def stdev(self):
return statistics.stdev(self._data)
class DataSet:
def __init__(self, sequence_of_numbers):
self._data = sequence_of_numbers
@property
@cache
def stdev(self):
return statistics.stdev(self._data)
sorted(iterable, key = cmp_to_key(locale.strcoll)) # locale - aware sort order
@lru_cache
def count_vowels(sentence):
return sum(sentence.count(vowel) for vowel in 'AEIOUaeiou')
@lru_cache(maxsize = 32)
def get_pep(num):
'Retrieve text of a Python Enhancement Proposal'
resource = 'https://www.python.org/dev/peps/pep-%04d/' % num
try:
with urllib.request.urlopen(resource) as s:
return s.read()
except urllib.error.HTTPError:
return 'Not Found'
>>>
for n in 8, 290, 308, 320, 8, 218, 320, 279, 289, 320, 9991:
...pep = get_pep(n)
...print(n, len(pep))
>>>
get_pep.cache_info()
CacheInfo(hits = 3, misses = 8, maxsize = 32, currsize = 8)