Why is my idea not working in python2?

Here is an idea for a dict subclass that can mutate keys. This is a simple, self-contained example, similar to a dict , but not case sensitive for str keys.

 from functools import wraps def key_fix_decorator(f): @wraps(f) def wrapped(self, *args, **kwargs): if args and isinstance(args[0], str): args = (args[0].lower(),) + args[1:] return f(self, *args, **kwargs) return wrapped class LowerDict(dict): pass for method_name in '__setitem__', '__getitem__', '__delitem__', '__contains__', 'get', 'pop', 'setdefault': new_method = key_fix_decorator(getattr(LowerDict, method_name)) setattr(LowerDict, method_name, new_method) 

dev note: if you copy my code for your own purposes, you must implement LowerDict.__init__ to check for any key collisions - I did not bother to include this for the purposes of this question

On python3, everything works fine:

 >>> d = LowerDict(potato=123, spam='eggs') >>> d['poTATo'] 123 >>> d.pop('SPAm') 'eggs' >>> d['A'] # KeyError: 'a' 

In python2 it is not even imported, here is the trace:

  File "/tmp/thing.py", line 15, in <module> new_method = key_fix_decorator(getattr(LowerDict, method_name)) File "/tmp/thing.py", line 4, in key_fix_decorator @wraps(f) File "/usr/lib/python2.7/functools.py", line 33, in update_wrapper setattr(wrapper, attr, getattr(wrapped, attr)) AttributeError: 'wrapper_descriptor' object has no attribute '__module__' 

What could be the problem? I do not see any version- basestring code except for the str / basestring , which is just a minor detail and not a code division issue.

+7
python decorator python-decorators
source share
1 answer

The version of functools.wraps() in Python 3 can handle function objects with some attributes that it copies on missing ones; one in Python 2 cannot. This is because issue No. 3445 was fixed only for Python 3; dict methods are defined in C code and do not have the __module__ attribute.

@wraps(f) decorator makes everything work in Python 2 @wraps(f) :

 >>> def key_fix_decorator(f): ... def wrapped(self, *args, **kwargs): ... if args and isinstance(args[0], str): ... args = (args[0].lower(),) + args[1:] ... return f(self, *args, **kwargs) ... return wrapped ... >>> class LowerDict(dict): ... pass ... >>> for method_name in '__setitem__', '__getitem__', '__delitem__', '__contains__', 'get', 'pop', 'setdefault': ... new_method = key_fix_decorator(getattr(LowerDict, method_name)) ... setattr(LowerDict, method_name, new_method) ... >>> d = LowerDict(potato=123, spam='eggs') >>> d['poTATo'] 123 >>> d.pop('SPAm') 'eggs' >>> d['A'] Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 5, in wrapped KeyError: 'a' 

You can replicate enough of what wraps does manually:

 def key_fix_decorator(f): def wrapped(self, *args, **kwargs): if args and isinstance(args[0], str): args = (args[0].lower(),) + args[1:] return f(self, *args, **kwargs) wrapped.__name__ = f.__name__ wrapped.__doc__ = f.__doc__ return wrapped 

or limit the attributes that wraps trying to copy:

 def key_fix_decorator(f): @wraps(f, assigned=('__name__', '__doc__')) def wrapped(self, *args, **kwargs): if args and isinstance(args[0], str): args = (args[0].lower(),) + args[1:] return f(self, *args, **kwargs) return wrapped 

You do not need to update the __module__ attribute here; which is mainly useful only for introspection.

+4
source share

All Articles