2011-11-02 17 views
22

組み込み型のサブクラス化では、組み込み型のメソッドの戻り型でPython 2とPython 3の間にかなり大きな違いがあることに気付きました。次のコードは設定のためにこれを示す:Python 2と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))) 

のPython 2では、すべての戻り値は、タイプMySetです。 Python 3では、戻り値の型はsetです。結果が想定されていることに関するドキュメントや、Python 3の変更に関するドキュメントを見つけることができませんでした。

とにかく、私が本当に気にしているのは、これがPython 3の簡単な方法です組み込み型のすべてのメソッドを再定義することなく、Python 2で見られる動作ですか?

+0

をバイパスするためs1' 'の唯一のタイプは、' S2の種類は関係ありません手動で特別なメソッド(__and__など)をラップする必要があるだろう'。 – agf

+2

'False + False'は' False'ではなく '0'であるのと似ています(' bool'は 'int'のサブクラスです)。 –

答えて

11

これは、Python 2.xから3.xに移動するときの組み込みタイプの一般的な変更ではありません。例えば、とは、2.xと3.xで同じ動作をします。 this bug tracker issueで説明されているように、セットタイプのみが変更されて他のタイプと一致しました。

私はそれが古い方法を動作させるための本当に良い方法はないと思います。

これは、すべてのメソッドを自分のタイプを返すバージョンで上書きするだけです。 (多くの方法があります!)

あなたのアプリケーションでは、おそらくcopy()を上書きして取り除き、インプレースメソッドに固執することができます。

+0

ここで、Python 2は一貫性がありませんでした。 Python 2で 'class MySet(set):pass'を作成すると' print type()。copy()) 'は' 'を返しますが、' class MyDict(dict):pass'の場合、 'print type(MyDict()。copy())'は ''となります。 – Cito

+0

少なくとも1回の操作で非特殊メソッドを処理する方法があります。私はどのように(私はコメントにコードを入れることはできません)説明するために私自身の質問に答えます。しかし、これはまだ私が望むオーバーヘッドです。一つ一つを扱う特別な方法があります。 – khinsen

0

おそらく、あなたはそれが簡単になるだろうのためのすべてが平凡なラッピング行うにはメタクラス:スベンの答えにフォローアップとして

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

いくつかのコメント:1.これは 'e()'と呼ばれるメソッドをラップすることができませんでしたが、 '__getattribute __()'をラップして、基本型のオブジェクトを属性に格納しません。 2.これは、特に属性を取得するために、重大なパフォーマンスヒットをします。属性にリストを格納すると、アクセスごとに反復されます。パフォーマンス上の問題があります。指摘するには多すぎるかもしれません。 –

+0

@SvenMarnach:なぜ 'e()'をラップできないのですか? –

+0

名前 'e'のため、' attr in( '__new __') 'という条件が成り立つでしょう。確かに、それは安いものですが、このコードにはもっと不明瞭なバグがあります。 –

0

が、ここではすべての非特別の世話をするユニバーサルラッピングソリューションですメソッド。アイデアは、メソッド呼び出しから来る最初のルックアップをキャッチし、型変換を行うラッパーメソッドをインストールすることです。その後のルックアップでは、ラッパーは直接戻されます。

警告:

1)これは私が私のコードを持っているしたいとより多くの魔法の策略です。

2)私はまだ彼らのルックアップは、Python 2オン__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 
関連する問題