21

Pythonの特定の状況は最近私を驚かせました、そしてその理由は少しの調査の後でまだ完全には明らかではありません。次のクラス定義は問題なく機能しているように見え、意図したものを生成します。

class A: __slots__ = 'a', 'b'
class B(A): __slots__ = ()
class C(A): __slots__ = ()
class D(B, C): __slots__ = ()

これらは、ダイヤモンドの継承パターンで配置された4つのクラスです。ただし、多少類似したパターンは許可されていません。次のクラス定義は、最初のクラス定義と同じように機能するように見えます。

class B: __slots__ = 'a', 'b'
class C: __slots__ = 'a', 'b'
class D(B, C): __slots__ = ()

Traceback (most recent call last):
  File "<pyshell#74>", line 1, in <module>
    class D(B, C): __slots__ = ()
TypeError: multiple bases have instance lay-out conflict

ただし、TypeErrorこの例ではaが発生します。したがって、3つの質問が発生します。(1)スロット名を考慮すると、これはPythonのバグですか?(2)そのような答えを正当化するものは何ですか?(3)最善の回避策は何ですか?


参照:

4

4 に答える 4

9

__slots__?を定義する複数のクラスから継承することはできません

選ぶ。

レイアウトの競合がある場合、 でないことを定義する複数のクラスから継承することはできません。__slots__

スロットには順序付けられたレイアウトがあり、クラスで作成される記述子はそれらの位置に依存しているため、多重継承の下でレイアウトの競合があってはなりません。

aそれぞれとbが異なるスロットと見なされ、レイアウトアルゴリズムがそれらが意味的に同じであるかどうかをチェックしないため、最も単純なアプローチは失敗します。

class B: __slots__ = 'a', 'b' # creates descriptors in B for a, b
class C: __slots__ = 'a', 'b' # creates new, different descriptors in C
class D(B, C): __slots__ = () # B.a or C.a comes first?

最初の例は、多重継承がAのスロットのみを取得するために機能します。したがって、すべてのケースでAの記述子と位置/レイアウトが使用されます。たとえば、次のことが許可されます。

class A: __slots__ = 'a', 'b' # shared parent, ok

class B(A): __slots__ = () # B or C must be empty

class C(A): __slots__ = 'c', # Since C is nonempty, B must be empty to inherit from both

class D(B, C): __slots__ = 'd', 'e'

Dをインスタンス化し、それらのスロットを使用します。

d = D()
d.a = d.b = d.c = d.d = d.e = 'foo'

また、変数を動的に作成することはできません。

>>> d.f = 'foo'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'D' object has no attribute 'f'

上記は問題のあるコードを解決するための1つのアプローチですが、少し書き直す必要がある場合がありBます。別のスロットが必要であると判断した場合は、Bの機能を抽象化して、Dのコードを再利用する必要があります(これは問題ありませんが、混乱を招く可能性があります)。

抽象化を使用することをお勧めします。別の解決策は、抽象クラスやミックスインに具象クラスの機能が含まれている場合に、これを行うことです。

class AbstractB: __slots__ = ()

class B(AbstractB): __slots__ = 'a', 'b'

class AbstractC: __slots__ = ()

class C(AbstractC): __slots__ = 'a', 'b'

class Mixin: __slots__ = ()

class D(AbstractB, AbstractC, Mixin): __slots__ = 'a', 'b'

最初の例は、レイアウトの競合を回避するため、非常に実用的です。これは、コンクリーションの代わりに抽象化を使用してソリューションを再考するだけです。

最後の質問:

(1)スロット名を考えると、これはPythonのバグですか?

いいえ、この問題について多くの混乱がありますが、それはある程度文書化されており、エラーはこの動作を明確にしようとします。

(2)そのような答えを正当化するものは何ですか?

スロットを定義するクラスは、データがどこに配置されるかを知っている記述子を取得します。レイアウトが変更されると、記述子が間違ってしまいます。

各サブクラスは独自のレイアウトと独自の記述子を作成できますか?私はそれが可能だと思いますが、それは彼らがどのように機能するかを少し書き直す必要があり、それを行うための政治的意志があり、Capiを突っ込んで現在の動作に依存している他のユーザーを壊す可能性があります。

(3)最善の回避策は何ですか?

「最良」を定義します。

書くのが最も速く、おそらく最も複雑ではありませんか?:最初の例のようにレイアウトの競合を避けてください。

ベストプラクティス?:抽象継承ツリーを使用し、コンクリーションにスロットを定義します。このアプローチではより多くのクラスが存在する可能性がありますが、他の人や「将来のあなた」が対処するのはおそらくそれほど複雑ではないかもしれません。

于 2018-10-31T17:44:41.833 に答える
1

クラスが__slots__を定義しないという制約を強制することにより、すべての子クラスに必要な特性を備えた特別なオブジェクトクラスを構築できます。このクラスは、通常のオブジェクトのエイリアスとして登録されています。

class _object: __slots__ = '_MetaSafe__exec', '__dict__'

class MetaSafe(type):

    __REGISTRY = {object: _object}

    @classmethod
    def clone(cls, old):
        return cls(old.__name__, old.__bases__, dict(old.__dict__), old)

    def __new__(cls, name, bases, classdict, old=None):
        # Check on a few classdict keys.
        assert '__new__' not in classdict, '__new__ must not be defined!'
        assert '__slots__' not in classdict, '__slots__ must not be defined!'
        assert '__module__' in classdict, '__module__ must be defined!'
        # Validate all the parent classes.
        valid = []
        for base in bases:
            if base in cls.__REGISTRY:
                valid.append(cls.__REGISTRY[base])
            elif base in cls.__REGISTRY.values():
                valid.append(base)
            else:
                valid.append(cls.clone(base))
        # Wrap callables without thread mark.
        for key, value in classdict.items():
            if callable(value):
                classdict[key] = cls.__wrap(value)
        # Fix classdict and create new class.
        classdict.update({'__new__': cls.__new, '__slots__': (), '__module__':
                          '{}.{}'.format(__name__, classdict['__module__'])})
        cls.__REGISTRY[old] = new = \
            super().__new__(cls, name, tuple(valid), classdict)
        return new

    def __init__(self, name, bases, classdict, old=None):
        return super().__init__(name, bases, classdict)

    @staticmethod
    def __wrap(func):
        @functools.wraps(func)
        def safe(self, *args, **kwargs):
            return self.__exec(func, self, *args, **kwargs)
        return safe

    @classmethod
    def __new(meta, cls, *args, **kwargs):
        self = object.__new__(cls, *args, **kwargs)
        if 'master' in kwargs:
            self.__exec = kwargs['master'].__exec
        else:
            array = tuple(meta.__REGISTRY.values())
            for value in args:
                if isinstance(value, array):
                    self.__exec = value.__exec
                    break
            else:
                self.__exec = Affinity()
        return self

tkinterこのコードは、クラスのクローンを作成することでスレッドセーフにするためのビルディングブロックとして使用できます。このAffinityクラスは、コードが単一のスレッドで実行されることを自動的に保証し、GUIエラーを防ぎます。

于 2012-06-04T19:42:33.600 に答える
-1

このエラーに直面したので、カスタムデータベースノードにスロットを使用したかったのです。これが私が作成したテストスイートです(Python 3.xで):

import logging

A = None, 'attr1', 'attr2', 'attr3', 'attr4'

class C12(object):
    __slots__ = (A[1], A[2])

class C1234(object):
    __slots__ = (A[1], A[2], A[3], A[4])

class C34(object):
    __slots__ = (A[3], A[4])

class C3byC12(C12):
    __slots__ = (A[3])

class CEmpty(object):
    __slots__ = ()

MSG_FRM = '\n\tc1: {}\n\tc2: {}\n\t__slots__: {}'
NOT_DEF = 'not defined'

def test(c1, c2, slots):
    logging.debug('*'*20 + ' new class test ' + '*'*20)
    msg = MSG_FRM.format(c1, c2, slots)
    try:
        if slots == NOT_DEF:
            class TestClass(c1, c2): pass
        else:        
            class TestClass(c1, c2):
                __slots__ = slots
    except TypeError:
        logging.exception('BOOM!!! ' + msg)
    else:
        logging.debug('No Boom! ' + msg)
        instance = TestClass()
        if '__dict__' in dir(instance):
            logging.warning('Instance has __dict__!')
        else:
            logging.debug('Instance __slots__:{}'.format(
                          instance.__slots__))
        logging.debug('Attributes in instance dir: {}'.format(
            ' '.join(['X' if (a in dir(instance)) else '_'
                     for a in A[1:]])))

if __name__ == '__main__':
    logging.basicConfig(level=logging.DEBUG)
    test(C12, C34, (A[2], A[4]))
    test(C12, C3byC12, (A[2],))
    test(C3byC12, C12, (A[4],))
    test(C1234, C34, (A[2], A[4]))
    test(C1234, CEmpty, (A[2], A[4]))
    test(C12, CEmpty, (A[2], A[4]))
    test(C12, CEmpty, (A[1], A[2]))
    test(C12, CEmpty, ())
    test(CEmpty, C1234, (A[2], A[4]))
    test(CEmpty, C12, (A[3],))
    test(C12, C34, NOT_DEF)
    test(C12, CEmpty, NOT_DEF)

結果は次のとおりです。

DEBUG:root:******************** new class test ********************
ERROR:root:BOOM!!!
        c1: <class '__main__.C12'>
        c2: <class '__main__.C34'>
        __slots__: ('attr2', 'attr4')
Traceback (most recent call last):
  File "boom.py", line 30, in test
    class TestClass(c1, c2):
TypeError: multiple bases have instance lay-out conflict
DEBUG:root:******************** new class test ********************
ERROR:root:BOOM!!!
        c1: <class '__main__.C12'>
        c2: <class '__main__.C3byC12'>
        __slots__: ('attr2',)
Traceback (most recent call last):
  File "boom.py", line 30, in test
    class TestClass(c1, c2):
TypeError: Cannot create a consistent method resolution
order (MRO) for bases C3byC12, C12
DEBUG:root:******************** new class test ********************
DEBUG:root:No Boom!
        c1: <class '__main__.C3byC12'>
        c2: <class '__main__.C12'>
        __slots__: ('attr4',)
DEBUG:root:Instance __slots__:('attr4',)
DEBUG:root:Attributes in instance dir: X X X X
DEBUG:root:******************** new class test ********************
ERROR:root:BOOM!!!
        c1: <class '__main__.C1234'>
        c2: <class '__main__.C34'>
        __slots__: ('attr2', 'attr4')
Traceback (most recent call last):
  File "boom.py", line 30, in test
    class TestClass(c1, c2):
TypeError: multiple bases have instance lay-out conflict
DEBUG:root:******************** new class test ********************
DEBUG:root:No Boom!
        c1: <class '__main__.C1234'>
        c2: <class '__main__.CEmpty'>
        __slots__: ('attr2', 'attr4')
DEBUG:root:Instance __slots__:('attr2', 'attr4')
DEBUG:root:Attributes in instance dir: X X X X
DEBUG:root:******************** new class test ********************
DEBUG:root:No Boom!
        c1: <class '__main__.C12'>
        c2: <class '__main__.CEmpty'>
        __slots__: ('attr2', 'attr4')
DEBUG:root:Instance __slots__:('attr2', 'attr4')
DEBUG:root:Attributes in instance dir: X X _ X
DEBUG:root:******************** new class test ********************
DEBUG:root:No Boom!
        c1: <class '__main__.C12'>
        c2: <class '__main__.CEmpty'>
        __slots__: ('attr1', 'attr2')
DEBUG:root:Instance __slots__:('attr1', 'attr2')
DEBUG:root:Attributes in instance dir: X X _ _
DEBUG:root:******************** new class test ********************
DEBUG:root:No Boom!
        c1: <class '__main__.C12'>
        c2: <class '__main__.CEmpty'>
        __slots__: ()
DEBUG:root:Instance __slots__:()
DEBUG:root:Attributes in instance dir: X X _ _
DEBUG:root:******************** new class test ********************
DEBUG:root:No Boom!
        c1: <class '__main__.CEmpty'>
        c2: <class '__main__.C1234'>
        __slots__: ('attr2', 'attr4')
DEBUG:root:Instance __slots__:('attr2', 'attr4')
DEBUG:root:Attributes in instance dir: X X X X
DEBUG:root:******************** new class test ********************
DEBUG:root:No Boom!
        c1: <class '__main__.CEmpty'>
        c2: <class '__main__.C12'>
        __slots__: ('attr3',)
DEBUG:root:Instance __slots__:('attr3',)
DEBUG:root:Attributes in instance dir: X X X _
DEBUG:root:******************** new class test ********************
ERROR:root:BOOM!!!
        c1: <class '__main__.C12'>
        c2: <class '__main__.C34'>
        __slots__: not defined
Traceback (most recent call last):
  File "boom.py", line 28, in test
    class TestClass(c1, c2): pass
TypeError: multiple bases have instance lay-out conflict
DEBUG:root:******************** new class test ********************
DEBUG:root:No Boom!
        c1: <class '__main__.C12'>
        c2: <class '__main__.CEmpty'>
        __slots__: not defined
WARNING:root:Instance has __dict__!
DEBUG:root:Attributes in instance dir: X X _ _

ご覧のとおり、2つのオプションがあります。

  1. __slots__ = ()親クラスの1つを除くすべてに対して定義するか、
  2. または、親の1つを別の親のサブクラスにします。

新しいクラスでも定義する必要があることに注意してください。__slots__そうしないと、が取得されます__dict__

于 2014-04-25T17:40:09.560 に答える
-2

この代替案を見たことがありますか? https://stackoverflow.com/a/53063670/1400467

_slots_メタクラスと偽の属性を使用することによる「トリッキーな」回避策があります。これはPython3.6で機能し、Python3.Xでも機能します。

于 2018-10-30T11:56:45.743 に答える