2014-12-02 11 views
7

Ecco un'idea per una sottoclasse di dict che può modificare le chiavi. Questo è un semplice esempio autonomo che è proprio come un dict ma non fa distinzione tra maiuscole e minuscole per le chiavi str.Perché la mia idea non funziona in python2?

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) 

nota dev: se si copia il mio codice per i propri usi, è necessario implementare LowerDict.__init__ per verificare eventuali collisioni chiave - non ho preso la briga di includere che ai fini di questa domanda

Su python3 tutto sembra funziona benissimo:

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

In python2 essa non ha nemmeno l'importazione, qui è la traceback:

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__' 

Quale potrebbe essere il problema? Non riesco a vedere alcun codice specifico per la versione eccetto per la cosa str/basestring, che è solo un piccolo dettaglio, non un problema di rottura del codice.

+1

La differenza è come 'update_wrapper' è implementato in entrambe le versioni.https: //hg.python.org/cpython/file/2.7/Lib/functools.py#l17 e https://hg.python.org/ cpython/file/3.4/Lib/functools.py # l43 –

+2

Questo è probabilmente correlato: http://bugs.python.org/issue3445 –

+0

Potresti anche essere interessato a PEP 455: https://www.python.org/ dev/peps/pep-0455 / –

risposta

4

La versione functools.wraps() in Python 3 è in grado di gestire gli oggetti funzione con alcuni degli attributi copiati mancanti; quello in Python 2 non può. Questo perché issue #3445 è stato corretto solo per Python 3; i metodi di dict sono definiti nel codice C e non dispongono dell'attributo __module__.

Tralasciando il @wraps(f) decoratore rende il lavoro tutto in Python 2 troppo:

>>> 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' 

è possibile replicare abbastanza di ciò wraps fa manualmente:

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 

o limitare gli attributi che wraps cerca di copia in tutta:

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 

Non è necessario aggiornare l'attributo __module__ qui; questo è principalmente utile solo per l'introspezione.