-
-
Notifications
You must be signed in to change notification settings - Fork 29.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Ignore missing attributes in functools.update_wrapper #47695
Comments
When trying to do something like I got this error message: Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "Aspyct.py", line 175, in beforeCall
_stickAdvice(function, theAdvice)
File "Aspyct.py", line 90, in _stickAdvice
functools.update_wrapper(theAdvice, function)
File "/usr/lib/python2.5/functools.py", line 33, in update_wrapper
setattr(wrapper, attr, getattr(wrapped, attr))
AttributeError: 'method_descriptor' object has no attribute '__module__' |
I think what you want is functools.update_wrapper(myWrapper, str.split, ('__name__', '__doc__')) |
The problem actually has to do with trying to use update_wrapper on a >>> import functools
>>> functools.WRAPPER_ASSIGNMENTS
('__module__', '__name__', '__doc__')
>>> functools.WRAPPER_UPDATES
('__dict__',)
>>> def f(): pass
...
>>> class C:
... def m(): pass
...
>>> set(dir(f)) - set(dir(C.m))
set(['func_closure', 'func_dict', '__module__', 'func_name',
'func_defaults', '__dict__', '__name__', 'func_code', 'func_doc',
'func_globals']) Is an exception the right response when encountering a missing Since this is an issue that doesn't come up with the main intended use |
Thank you for considering this report :) |
Another possibility would be for methods to also get the __name__ and |
IMO, I'd be better to always ignore missing attributes. Consider another from functools import wraps, partial
def never_throw(f):
@wraps(f)
def wrapper(*args, **kwargs):
try: return f(*args, **kwargs)
except: pass
return wrapper Looks reasonable. But if I write I was to use some additional parameters to @wraps, I would have to |
If the object being wrapped isn't a plain vanilla function, then the So for both the use cases mentioned thus far, the AttributeError appears |
I'm also interested in seeing this fixed. In the current behavior, the following code doesn't work: <<< start code def magic(func):
@wraps(func)
def even_more_magic(*args):
return func(*args)
return even_more_magic
class Frob(object):
@magic
@classmethod
def hello(cls):
print '%r says hello' % (cls,)
>>> end code It fails because classmethods don't have a module attribute, as commented upon elsewhere in this issue. To fix this, you'd either have to either pass in an This seems arbitrary and unnecessarily complex; skipping over a missing module should be just fine. Mixing I've attached a trivial patch which just ignores missing "assigned" attributes. |
The patch should come with an unit test (in Lib/test/test_functools.py). |
New patch included, with a test case. I had wanted to check the classmethod __module__ thing directly, but that proved to be elusive, since the classmethod gets the __module__ attribute if the module is '__main__', and you can't delete that attribute. My test just tries to assign another attribute which doesn't exist. I just tried to copy the style of the rest of the module, lmk if there are any problems. |
In your test, the more common convention is to use assertFalse(foo) instead of assert_(not foo). |
I think it would be better to test with some of the real world examples given in this issue: str.split, and a functools.partial object. |
As an extra data point: we just hit this problem in Django ticket bpo-13093 (http://code.djangoproject.com/ticket/13093). In our case, a decorator was using wraps(); however, that decorator was breaking when it was used on a class with a __call__ method, because the instance of the class doesn't have a __name__ attribute. We've implemented the proposed workaround (i.e., check the attributes that are available and provide that tuple as the assigned argument), but I don't agree that this should be expected behavior. wraps() is used to make a decorated callable look like the callable that is being decorated; if there are different types of callable objects, I would personally expect wraps() to adapt to the differences, not raise an error if it sees anything other than a function. True, some attributes (like __doc__) won't always be correct as a result of wrapping on non-vanilla functions -- but then, that's true of plain vanilla functions, too. A decorator wrapping a function can fundamentally change what the wrapped function does, and there's no guarantee that the docstring for the wrapped function will still be correct after decoration. |
Patch updated: bound and unbound methods, user-defined callable, partial object included in test. By the way, >>> [id(abs.__doc__) for i in range(5)]
[140714383081744, 140714383081744, 140714383081744, 140714383081744, 140714383081744]
>>> [id(s) for s in [abs.__doc__ for i in range(5)]]
[140714383084040, 140714383082976, 140714383083144, 140714383075904, 140714383081744] How it can be explained? Built-in functions (and methods) _sometimes_ return a new instance of its '__doc__' (and '__name__'), and sometimes does not. |
To Evan Klitzke (eklitzke):
This code _should not_ work. [Unbound] classmethod object is not a method or a function, it is even not a callable; it is a descriptor (returning callable). So, it cannot be wrapped or decorated in such way. If you want something like this to work, you probably should place @classmethod on the upper level (in other words, apply classmethod after all other decorators): @classmethod
@magic
def hello(cls):
print '%r says hello' % (cls,) |
Is there anyone who can provide this issue with a bit of TLC as it's almost the 2nd birthday? |
That would be me :) In line with the 'consenting adults' philosophy and with the current behaviour causing real world problems, I'll accept this RFE and check it in soon. |
Implemented in r84132 (not based on this patch though). |
Shouldn't this be fixed in 2.7 as well? It's a bug, it's in the wild, and it's causing people to do ugly (and maybe not 100% reliable) things like https://code.djangoproject.com/browser/django/trunk/django/utils/decorators.py#L68 |
Well, Nick judged that this was not a bug per se, but rather a request for enhancement, thus it was only committed to 3.3. |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: