18

セットとほぼ同じオブジェクトを実装していますが、追加のインスタンス変数が必要なため、組み込みのセット オブジェクトをサブクラス化しています。オブジェクトの 1 つがコピーされたときに、この変数の値が確実にコピーされるようにする最善の方法は何ですか?

古いセット モジュールを使用すると、次のコードは完全に機能しました。

import sets
class Fooset(sets.Set):
    def __init__(self, s = []):
        sets.Set.__init__(self, s)
        if isinstance(s, Fooset):
            self.foo = s.foo
        else:
            self.foo = 'default'
f = Fooset([1,2,4])
f.foo = 'bar'
assert( (f | f).foo == 'bar')

しかし、これは組み込みの set モジュールを使用すると機能しません。

私が見ることができる唯一の解決策は、コピーされたセットオブジェクトを返すすべてのメソッドをオーバーライドすることです...その場合、セットオブジェクトをサブクラス化する必要はありません。確かにこれを行う標準的な方法はありますか?

(明確にするために、次のコードは機能しませ(アサーションは失敗します)。

class Fooset(set):
    def __init__(self, s = []):
        set.__init__(self, s)
        if isinstance(s, Fooset):
            self.foo = s.foo
        else:
            self.foo = 'default'

f = Fooset([1,2,4])
f.foo = 'bar'
assert( (f | f).foo == 'bar')

)

4

8 に答える 8

20

組み込みコレクションのメソッドをラップする私のお気に入りの方法:

class Fooset(set):
    def __init__(self, s=(), foo=None):
        super(Fooset,self).__init__(s)
        if foo is None and hasattr(s, 'foo'):
            foo = s.foo
        self.foo = foo



    @classmethod
    def _wrap_methods(cls, names):
        def wrap_method_closure(name):
            def inner(self, *args):
                result = getattr(super(cls, self), name)(*args)
                if isinstance(result, set) and not hasattr(result, 'foo'):
                    result = cls(result, foo=self.foo)
                return result
            inner.fn_name = name
            setattr(cls, name, inner)
        for name in names:
            wrap_method_closure(name)

Fooset._wrap_methods(['__ror__', 'difference_update', '__isub__', 
    'symmetric_difference', '__rsub__', '__and__', '__rand__', 'intersection',
    'difference', '__iand__', 'union', '__ixor__', 
    'symmetric_difference_update', '__or__', 'copy', '__rxor__',
    'intersection_update', '__xor__', '__ior__', '__sub__',
])

基本的に、自分の回答で行っていることと同じですが、loc が少なくなります。リストやディクテーションでも同じことをしたい場合は、メタクラスに入れるのも簡単です。

于 2009-04-30T01:01:00.863 に答える
11

setこれを行うための推奨される方法は、組み込みの から直接サブクラス化することではなく、 collections.abcで利用可能な抽象基本クラスSetを利用することだと思います。

ABC Set を使用すると、いくつかのメソッドがミックスインとして無料で提供されるため__contains__()、 、 、__len__()およびのみを定義することで最小限の Set クラスを作成できます__iter__()intersection()やのようなより優れた set メソッドが必要なdifference()場合は、おそらくそれらをラップする必要があります。

これが私の試みです(これはたまたまフリーズセットのようですが、から継承しMutableSetて変更可能なバージョンを取得できます):

from collections.abc import Set, Hashable

class CustomSet(Set, Hashable):
    """An example of a custom frozenset-like object using
    Abstract Base Classes.
    """
    __hash__ = Set._hash

    wrapped_methods = ('difference',
                       'intersection',
                       'symetric_difference',
                       'union',
                       'copy')

    def __repr__(self):
        return "CustomSet({0})".format(list(self._set))

    def __new__(cls, iterable=None):
        selfobj = super(CustomSet, cls).__new__(CustomSet)
        selfobj._set = frozenset() if iterable is None else frozenset(iterable)
        for method_name in cls.wrapped_methods:
            setattr(selfobj, method_name, cls._wrap_method(method_name, selfobj))
        return selfobj

    @classmethod
    def _wrap_method(cls, method_name, obj):
        def method(*args, **kwargs):
            result = getattr(obj._set, method_name)(*args, **kwargs)
            return CustomSet(result)
        return method

    def __getattr__(self, attr):
        """Make sure that we get things like issuperset() that aren't provided
        by the mix-in, but don't need to return a new set."""
        return getattr(self._set, attr)

    def __contains__(self, item):
        return item in self._set

    def __len__(self):
        return len(self._set)

    def __iter__(self):
        return iter(self._set)
于 2011-07-14T19:15:53.363 に答える
4

残念なことに、 set は規則に従わず、型を保持していても__new__新しいsetオブジェクトを作成するために呼び出されません。これは明らかに Python のバグです (issue #1721812、2.xシーケンスでは修正されません)。typeX オブジェクトを作成するオブジェクトを呼び出さずに、型 X のオブジェクトを取得することはできません。set.__or__呼び出さない場合は、サブクラス オブジェクトではなく、オブジェクト__new__を返すことが正式に義務付けられています。set

しかし、実際には、上記のnoskloの投稿に注目すると、あなたの元の動作には意味がありません。Set.__or__オペレーターは、結果を構築するためにソース オブジェクトのいずれかを再利用するべきではありません。新しいオブジェクトを作成する必要fooがあります"default"

したがって、実際には、これを行う人は、どのコピーが使用されるかを知るために、これらの演算子をオーバーロードする必要fooがあります。結合されるフットセットに依存しない場合は、それをクラスのデフォルトにすることができます。その場合、新しいオブジェクトはそれがサブクラス タイプであると見なされるため、優先されます。

私が言いたいのは、あなたがこれをした場合、あなたの例はうまくいくということです:

class Fooset(set):
  foo = 'default'
  def __init__(self, s = []):
    if isinstance(s, Fooset):
      self.foo = s.foo

f = Fooset([1,2,5])
assert (f|f).foo == 'default'
于 2012-09-07T13:53:11.933 に答える
2

set1 | set2どちらの既存のものも変更せず、代わりにset新しいものを返す操作ですset。新しいsetものが作成され、返されます。 メソッドを定義してオペレータをカスタマイズせずに、いずれかまたは両方setの から新しく作成された に任意の属性を自動的にコピーする方法はありません。set|__or__

class MySet(set):
    def __init__(self, *args, **kwds):
        super(MySet, self).__init__(*args, **kwds)
        self.foo = 'nothing'
    def __or__(self, other):
        result = super(MySet, self).__or__(other)
        result.foo = self.foo + "|" + other.foo
        return result

r = MySet('abc')
r.foo = 'bar'
s = MySet('cde')
s.foo = 'baz'

t = r | s

print r, s, t
print r.foo, s.foo, t.foo

版画:

MySet(['a', 'c', 'b']) MySet(['c', 'e', 'd']) MySet(['a', 'c', 'b', 'e', 'd'])
bar baz bar|baz
于 2009-04-28T15:29:24.273 に答える
2

c コードでバイパス__init__を設定しているようです。ただし、 のインスタンスを終了すると、フィールドをコピーする機会がなくなります。Fooset

この場合、新しいセットを返すメソッドをオーバーライドする以外に、できることが多すぎるかどうかはわかりません。Set は明らかに一定の速度のために構築されているため、c で多くの作業を行います。

于 2009-04-28T15:59:59.097 に答える
1

私は次のように読んでいる質問に答えようとしています:「「セット」の演算子の戻り値をセットのサブクラスのタイプにする方法。私の読みが正しければ、重複する自分の質問からここに来ました。

この回答は、次のように他の回答とは異なります。

  • 指定されたクラス (サブクラス) は、デコレータを追加することによってのみ変更されます
  • したがって、指定されたクラス (hasattr(s, 'foo')) の詳細を気にしないほど一般的です。
  • 追加コストは、すべてのインスタンスに対してではなく、クラス (装飾されたとき) ごとに 1 回支払われます。
  • 「セット」に固有の特定の例の唯一の問題は、簡単に定義できるメソッドのリストです。
  • 基本クラスは抽象ではなく、それ自体をコピー構築できると仮定します (そうでない場合は、基本クラスのインスタンスからコピーする __init__ メソッドを実装する必要があります)。

プロジェクトまたはモジュールのどこにでも配置できるライブラリ コード:

class Wrapfuncs:
  def __init__(self, *funcs):
    self._funcs = funcs

  def __call__(self, cls):
    def _wrap_method(method_name):
      def method(*args, **kwargs):
          result = getattr(cls.__base__, method_name)(*args, **kwargs)
          return cls(result)
      return method

    for func in self._funcs:
      setattr(cls, func, _wrap_method(func))
    return cls

セットで使用するには、新しいインスタンスを返すメソッドのリストが必要です。

returning_ops_funcs = ['difference', 'symmetric_difference', '__rsub__', '__or__', '__ior__', '__rxor__', '__iand__', '__ror__', '__xor__', '__sub__', 'intersection', 'union', '__ixor__', '__and__', '__isub__', 'copy']

クラスで使用できます

@Wrapfuncs(*returning_ops_funcs)
class MySet(set):
  pass

このクラスの何が特別なのか、詳細は割愛します。

次の行でコードをテストしました。

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

print(s1&s2)
print(s1.intersection(s2))
print(s1 and s2)
print(s1|s2)
print(s1.union(s2))
print(s1|s2|s3)
print(s1.union(s2, s3))
print(s1 or s2)
print(s1-s2)
print(s1.difference(s2))
print(s1^s2)
print(s1.symmetric_difference(s2))

print(s1 & set(s2))
print(set(s1) & s2)

print(s1.copy())

どの印刷:

MySet({2, 3})
MySet({2, 3})
MySet({2, 3, 4})
MySet({1, 2, 3, 4})
MySet({1, 2, 3, 4})
MySet({1, 2, 3, 4, 5})
MySet({1, 2, 3, 4, 5})
MySet({1, 2, 3})
MySet({1})
MySet({1})
MySet({1, 4})
MySet({1, 4})
MySet({2, 3})
{2, 3}
MySet({1, 2, 3})

結果が最適でない場合が 1 つあります。これは、演算子がクラスのインスタンスを右側のオペランドとして使用し、組み込みの「セット」のインスタンスを最初として使用する場合です。私はこれが好きではありませんが、この問題は私が見たすべての提案された解決策に共通していると思います.

collections.abc.Set が使用されている例を提供することも考えています。それは次のように行うことができますが:

from collections.abc import Set, Hashable
@Wrapfuncs(*returning_ops_funcs)
class MySet(set, Set):
  pass

@bjmcが念頭に置いていた利点があるのか​​ 、それとも「無料」で提供される「いくつかの方法」が何であるかはわかりません。このソリューションは、基本クラスを使用して作業を行い、サブクラスのインスタンスを返すことを目的としています。メンバーオブジェクトを使用して作業を行うソリューションは、おそらく同様の方法で生成できます。

于 2021-01-27T15:25:08.800 に答える
0

他の答えが正しく、すべてのメソッドをオーバーライドすることがこれを行う唯一の方法であると仮定すると、これを行うための適度にエレガントな方法を試してみます。さらにインスタンス変数が追加された場合、コードを 1 つだけ変更する必要があります。残念ながら、新しい二項演算子がセット オブジェクトに追加されると、このコードは壊れますが、それを回避する方法はないと思います。コメント大歓迎!

def foocopy(f):
    def cf(self, new):
        r = f(self, new)
        r.foo = self.foo
        return r
    return cf

class Fooset(set):
    def __init__(self, s = []):
        set.__init__(self, s)
        if isinstance(s, Fooset):
            self.foo = s.foo
        else:
            self.foo = 'default'

    def copy(self):
        x = set.copy(self)
        x.foo = self.foo
        return x

    @foocopy
    def __and__(self, x):
        return set.__and__(self, x)

    @foocopy
    def __or__(self, x):
        return set.__or__(self, x)

    @foocopy
    def __rand__(self, x):
        return set.__rand__(self, x)

    @foocopy
    def __ror__(self, x):
        return set.__ror__(self, x)

    @foocopy
    def __rsub__(self, x):
        return set.__rsub__(self, x)

    @foocopy
    def __rxor__(self, x):
        return set.__rxor__(self, x)

    @foocopy
    def __sub__(self, x):
        return set.__sub__(self, x)

    @foocopy
    def __xor__(self, x):
        return set.__xor__(self, x)

    @foocopy
    def difference(self, x):
        return set.difference(self, x)

    @foocopy
    def intersection(self, x):
        return set.intersection(self, x)

    @foocopy
    def symmetric_difference(self, x):
        return set.symmetric_difference(self, x)

    @foocopy
    def union(self, x):
        return set.union(self, x)


f = Fooset([1,2,4])
f.foo = 'bar'
assert( (f | f).foo == 'bar')
于 2009-04-28T16:31:30.177 に答える
-2

私にとって、これはWin32でPython2.5.2を使用して完全に機能します。クラス定義と次のテストを使用します。

f = Fooset([1,2,4])
s = sets.Set((5,6,7))
print f, f.foo
f.foo = 'bar'
print f, f.foo
g = f | s
print g, g.foo
assert( (f | f).foo == 'bar')

私はこの出力を取得します。これは私が期待するものです。

Fooset([1, 2, 4]) default
Fooset([1, 2, 4]) bar
Fooset([1, 2, 4, 5, 6, 7]) bar
于 2009-04-28T15:39:34.263 に答える