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.