2011-11-02 14 views
22

Quando si sottoclasse i tipi di builtin, ho notato una differenza piuttosto importante tra Python 2 e Python 3 nel tipo restituito dei metodi dei tipi built-in. Il codice seguente illustra questo per gli insiemi:Tipi di sottoclassi incorporati in Python 2 e Python 3

class MySet(set): 

    pass 

s1 = MySet([1, 2, 3, 4, 5]) 

s2 = MySet([1, 2, 3, 6, 7]) 

print(type(s1.union(s2))) 

print(type(s1.intersection(s2))) 

print(type(s1.difference(s2))) 

Con Python 2, tutti i valori di ritorno sono di tipo MySet. Con Python 3, i tipi di ritorno sono set. Non sono riuscito a trovare alcuna documentazione sul cambiamento in Python 3.

In ogni caso, quello che mi interessa davvero è questo: c'è un modo semplice in Python 3 per ottenere il comportamento visto in Python 2, senza ridefinire ogni singolo metodo dei tipi built-in?

+0

su Python 2 solo il tipo di 's1' è rilevante non è il tipo di' s2 '. – agf

+2

È simile al modo in cui 'False + False' è' 0', non 'False' (' bool' è una sottoclasse di 'int', comunque). –

risposta

11

Questo non è un cambiamento generale per i tipi incorporati quando si passa da Python 2.x a 3.x - list e int, ad esempio, hanno lo stesso comportamento in 2.xe 3.x. È stato modificato solo il tipo di set per portarlo in linea con gli altri tipi, come discusso in this bug tracker issue.

Ho paura che non ci sia un modo veramente carino per farlo comportarsi alla vecchia maniera. Ecco un po 'di codice sono stato in grado di venire con:

class MySet(set): 
    def copy(self): 
     return MySet(self) 
    def _make_binary_op(in_place_method): 
     def bin_op(self, other): 
      new = self.copy() 
      in_place_method(new, other) 
      return new 
     return bin_op 
    __rand__ = __and__ = _make_binary_op(set.__iand__) 
    intersection = _make_binary_op(set.intersection_update) 
    __ror__ = __or__ = _make_binary_op(set.__ior__) 
    union = _make_binary_op(set.update) 
    __sub__ = _make_binary_op(set.__isub__) 
    difference = _make_binary_op(set.difference_update) 
    __rxor__ = xor__ = _make_binary_op(set.__ixor__) 
    symmetric_difference = _make_binary_op(set.symmetric_difference_update) 
    del _make_binary_op 
    def __rsub__(self, other): 
     new = MySet(other) 
     new -= self 
     return new 

Questo semplicemente sovrascrivere tutti i metodi con le versioni che restituiscono il proprio tipo. (C'è un sacco di metodi!)

Forse per la vostra applicazione, è possibile scappare con la sovrascrittura copy() e attenersi ai metodi sul posto.

+0

A destra, Python 2 non era coerente qui. Se create un 'class MySet (set): pass' in Python 2, quindi' print type (MySet(). Copy()) 'fornisce' ', ma se create una' classe MyDict (dict): pass', quindi 'print type (MyDict(). Copy())' restituisce ''. – Cito

+0

C'è un modo per gestire almeno i metodi non speciali in una singola operazione. Risponderò alla mia domanda per illustrare come (non riesco a inserire il codice in un commento). Ma è ancora molto di più che mi piacerebbe, con tutti i metodi speciali per gestire uno per uno. – khinsen

0

Forse un metaclasse per fare tutto questo involucro monotona per voi sarebbe più facile:

class Perpetuate(type): 
    def __new__(metacls, cls_name, cls_bases, cls_dict): 
     if len(cls_bases) > 1: 
      raise TypeError("multiple bases not allowed") 
     result_class = type.__new__(metacls, cls_name, cls_bases, cls_dict) 
     base_class = cls_bases[0] 
     known_attr = set() 
     for attr in cls_dict.keys(): 
      known_attr.add(attr) 
     for attr in base_class.__dict__.keys(): 
      if attr in ('__new__'): 
       continue 
      code = getattr(base_class, attr) 
      if callable(code) and attr not in known_attr: 
       setattr(result_class, attr, metacls._wrap(base_class, code)) 
      elif attr not in known_attr: 
       setattr(result_class, attr, code) 
     return result_class 
    @staticmethod 
    def _wrap(base, code): 
     def wrapper(*args, **kwargs): 
      if args: 
       cls = args[0] 
      result = code(*args, **kwargs) 
      if type(result) == base: 
       return cls.__class__(result) 
      elif isinstance(result, (tuple, list, set)): 
       new_result = [] 
       for partial in result: 
        if type(partial) == base: 
         new_result.append(cls.__class__(partial)) 
        else: 
         new_result.append(partial) 
       result = result.__class__(new_result) 
      elif isinstance(result, dict): 
       for key in result: 
        value = result[key] 
        if type(value) == base: 
         result[key] = cls.__class__(value) 
      return result 
     wrapper.__name__ = code.__name__ 
     wrapper.__doc__ = code.__doc__ 
     return wrapper 

class MySet(set, metaclass=Perpetuate): 
    pass 

s1 = MySet([1, 2, 3, 4, 5]) 

s2 = MySet([1, 2, 3, 6, 7]) 

print(s1.union(s2)) 
print(type(s1.union(s2))) 

print(s1.intersection(s2)) 
print(type(s1.intersection(s2))) 

print(s1.difference(s2)) 
print(type(s1.difference(s2))) 
+0

Alcuni commenti: 1. Ciò non riuscirebbe a racchiudere un metodo chiamato 'e()', ma avvolge '__getattribute __()', impedendo di memorizzare gli oggetti del tipo di base negli attributi. 2. Ciò avrà un grave calo delle prestazioni, in particolare per il recupero degli attributi.Se si memorizza una lista in un attributo, verrà iterata su ogni accesso. Ci sono più problemi di prestazioni, forse troppi da segnalare. –

+0

@SvenMarnach: Perché non riuscirà a racchiudere 'e()'? –

+0

Perché per il nome 'e', la condizione' attr in ('__new __') 'manterrà. Certo, è economico, ma ci sono altri bug oscuri in questo codice. –

0

Come follow-up alla risposta di Sven, ecco una soluzione involucro universale che si prende cura di tutti i non-speciale metodi. L'idea è di catturare la prima ricerca proveniente da una chiamata al metodo e installare un metodo wrapper che esegue la conversione del tipo. Alle ricerche successive, il wrapper viene restituito direttamente.

Avvertenze:

1) Si tratta di inganno più magico di quanto mi piacerebbe avere nel mio codice.

2) avevo ancora bisogno di avvolgere metodi speciali (__and__ ecc) manualmente perché la loro ricerca bypassa __getattribute__

import types 

class MySet(set): 

    def __getattribute__(self, name): 
     attr = super(MySet, self).__getattribute__(name) 
     if isinstance(attr, types.BuiltinMethodType): 
      def wrapper(self, *args, **kwargs): 
       result = attr(self, *args, **kwargs) 
       if isinstance(result, set): 
        return MySet(result) 
       else: 
        return result 
      setattr(MySet, name, wrapper) 
      return wrapper 
     return attr