2011-11-02 28 views
22

Podczas tworzenia podklas typów wbudowanych zauważyłem dość istotną różnicę między Python 2 i Python 3 w typach zwracanych metod typów wbudowanych. Następujący kod ilustruje to dla zestawów:Typy wbudowanych podklas w Pythonie 2 i Pythonie 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))) 

Pythona 2, wszystkie wartości powrotne typu MySet. W języku Python 3 typy zwrotów to set. Nie mogłem znaleźć żadnej dokumentacji na temat tego, jaki wynik powinien być, ani żadnej dokumentacji na temat zmiany w Pythonie 3.

W każdym razie, na czym mi naprawdę zależy, to: czy istnieje prosty sposób w Pythonie 3, aby uzyskać zachowanie widziane w Pythonie 2, bez ponownego definiowania każdej metody wbudowanych typów?

+0

W Pythonie 2 tylko typ 's1' nie ma znaczenia rodzaj' s2 '. – agf

+2

Jest to podobne do tego, że 'False + False' ma wartość' 0', a nie 'False' (' bool' jest podklasą 'int', nawiasem mówiąc). –

Odpowiedz

11

To nie jest generalna zmiana dla wbudowanych typów podczas przenoszenia z wersji Python 2.x do 3.x - list i int, na przykład, zachowuje się tak samo w wersjach 2.x i 3.x. Zmieniono tylko typ zestawu, aby dostosować go do innych typów, jak omówiono w this bug tracker issue.

Obawiam się, że nie ma naprawdę fajnego sposobu na zachowanie starego stylu. Oto kod, który udało mi się wymyślić:

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 

To po prostu zastąpi wszystkie metody wersjami, które zwracają twój własny typ. (Istnieje wiele metod!)

Może dla twojej aplikacji możesz uniknąć nadpisywania copy() i trzymać się metod lokalnych.

+0

Dobrze, Python 2 nie był tutaj spójny. Jeśli utworzysz 'class MySet (set): pass' w Pythonie 2, to' print type (MySet(). Copy()) 'daje' ', ale jeśli utworzysz klasę' ' MyDict (dict): pass', a następnie 'print type (MyDict(). Copy())' daje ''. – Cito

+0

Istnieje sposób na obsłużenie przynajmniej nietypowych metod w ramach jednej operacji. Odpowiem na własne pytanie, aby zilustrować, w jaki sposób (nie mogę umieścić kodu w komentarzu). Ale wciąż jeszcze bardziej zależy mi na tym, by wszystkie specjalne metody obsługiwać jeden po drugim. – khinsen

0

Może metaklasa aby zrobić wszystko, szarej opakowania dla byś łatwiej:

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

Kilka uwag: 1. To nie zawiodłoby metody zwanej 'e()', ale powoduje zawijanie '__getattribute __()', zapobiegając przechowywaniu obiektów typu podstawowego w atrybutach. 2. Będzie to miało silne działanie, szczególnie w przypadku pobierania atrybutów.Jeśli przechowujesz listę w atrybucie, będzie ona iterowana przy każdym dostępie. Jest więcej problemów z wydajnością, może zbyt wiele do wskazania. –

+0

@SvenMarnach: Dlaczego nie uda się owinąć 'e()'? –

+0

Ponieważ dla nazwy 'e', zachowany będzie warunek' attr in ('__new __') '. Trzeba przyznać, że jest to tani, ale w tym kodzie jest więcej niejasnych błędów. –

0

W nawiązaniu do odpowiedzi Svena, o to uniwersalne rozwiązanie do pakowania, które dba o wszystko bez specjalnego metody. Pomysł polega na przechwyceniu pierwszego wyszukiwania pochodzącego z wywołania metody i zainstalowaniu metody opakowującej, która dokonuje konwersji typu. Podczas kolejnych wyszukiwań opakowanie jest zwracane bezpośrednio.

Ostrzeżenia:

1) Jest to więcej niż magia oszustwa chciałbym mieć w moim kodu.

2) Chciałbym jeszcze trzeba owinąć specjalnych metod (__and__ itp) ręcznie, ponieważ ich wyszukiwanie omija __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 
Powiązane problemy