2012-06-01 13 views
12

Una certa situazione in Python mi ha recentemente allarmato, e la sua ragione non è ancora del tutto chiara dopo una piccola ricerca. Le seguenti definizioni di classe sembrano lavorare senza problemi e produrranno ciò che si intende:Impossibile ereditare da più classi che definiscono __slots__?

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

Ci sono quattro classi disposte in un modello di diamante ereditarietà. Tuttavia, un modello un po 'simile non è permesso. Le seguenti definizioni delle classi sembrano come se essi funzionano lo stesso come il primo:

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 

Tuttavia, un TypeError viene sollevata in questo esempio. Quindi sorgono tre domande: (1) Si tratta di un bug in Python, considerando i nomi degli slot? (2) Cosa giustificherebbe una simile risposta? (3) Qual è la soluzione migliore?


Riferimenti:

+2

Confesso di non averlo capito al 100%, ma in base alle fonti collegate, non sembra che si tratti di un bug.Come ho detto prima, non sono al 100% su questo, ma la migliore "soluzione" sembra essere quella di limitare l'uso di '__slots__'. C'è una ragione specifica per cui devi usarli? –

+2

Vengono generati automaticamente da una metaclora per memorizzare un attributo di istanza magica al di fuori del suo dizionario. Il sistema esegue automaticamente una conversione su tutte le classi base e porta al problema dell'ereditarietà multipla. –

risposta

2

Forzando un vincolo che nessuna classe definisce __slots__, una classe oggetto speciale potrebbe essere costruita con le caratteristiche desiderate per tutte le classi figlie. La classe è registrata come alias per oggetti regolari.

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 

Questo codice può essere usato come un blocco di costruzione per rendere tkinter thread-safe clonando sue classi. La classe Affinity garantisce automaticamente che il codice venga eseguito su un singolo thread, impedendo errori della GUI.

-2
class superSlots: 
     @property 
     def __slots__(self):return self.MY_SLOTS 
class A(superSlots): 
     MY_SLOTS = "A","B" 
class B(superSlots): 
     MY_SLOTS = "A","B" 
class C(A,B): 
     MY_SLOTS = "X","Y" 

forse ?? non positivo sarebbe il metodo migliore ma penso che funzionerebbe bene

+4

Penso che '__slots__' sia troppo magico e trattato appositamente per far funzionare tutto questo. Sospetto anche che ti manchi il punto. –

+0

CPython 2.7 e 3.2 entrambi dicono 'TypeError: Errore quando si chiama l'oggetto 'proprietà' delle basi metaclea non è iterable' quando si tenta di definire' __slots__' come proprietà - non avrebbe senso calcolare "__slots__" in modo dinamico come questo . – James

+0

Grazie per aver cercato di fornire una risposta! I risultati finali di questo lavoro possono essere visti come http://code.activestate.com/recipes/578152 (threadbox.py). Risolve il problema delle slot. –

0

Ho riscontrato questo errore e volevo davvero utilizzare gli slot per i miei nodi di database personalizzati. Qui è la suite di test che ho fatto (il suo in 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) 

Ecco i risultati:

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 _ _ 

Come si può vedere ci sono due opzioni:

  1. Definire __slots__ =() per tutte tranne una delle classi parent,
  2. o creare uno dei genitori in sottoclasse dell'altro.

Si noti che è necessario definire __slots__ anche nella nuova classe, altrimenti si ottiene __dict__.