2012-06-01 11 views
12

Una cierta situación en Python me alarmó recientemente, y su razón aún no está completamente clara después de una pequeña investigación. Las siguientes definiciones de clase parecen trabajar sin problemas y producirán lo que se pretende:¿No se puede heredar de varias clases que definen __slots__?

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

Hay cuatro clases dispuestos en un patrón de herencia de diamante. Sin embargo, un patrón algo similar no está permitido. Las siguientes definiciones de clase parecen como si están funcionando el mismo que el primero:

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 

Sin embargo, un TypeError se eleva en este ejemplo. Así que surgen tres preguntas: (1) ¿Es esto un error en Python, teniendo en cuenta los nombres de las tragamonedas? (2) ¿Qué justificaría tal respuesta? (3) ¿Cuál es la mejor solución?


Referencias:

+2

Confieso que no estoy 100% entendiendo esto tampoco, pero basado en las fuentes que vinculó, no parece que sea un error.Como dije antes, no estoy al 100% en esto, pero la mejor "solución alternativa" parece ser limitar el uso de '__slots__'. ¿Hay alguna razón específica por la que tengas que usarlos? –

+2

Están siendo generados automáticamente por una metaclase para almacenar un atributo de instancia mágica fuera de su diccionario. El sistema ejecuta automáticamente una conversión en todas las clases base, lo que conduce al problema de herencia múltiple. –

Respuesta

2

Al forzar una restricción de que ninguna clase define __slots__, se podría construir una clase de objeto especial con las características deseadas para todas las clases secundarias. La clase está registrada como un alias para objetos regulares.

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 

Este código se puede utilizar como un bloque de construcción para hacer tkinter thread-safe mediante la clonación de sus clases. La clase Affinity asegura automáticamente que el código se ejecuta en un solo subproceso, evitando errores de la 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" 

tal vez ?? no positivo, sería el mejor método pero creo que funcionaría bien

+4

Creo que '__slots__' es demasiado mágico y especialmente tratado para que funcione. También sospecho que te estás perdiendo el punto. –

+0

CPython 2.7 y 3.2 ambos dicen 'TypeError: Error al llamar a las bases de la metaclase 'propiedad' objeto no es iterable' al tratar de definir' __slots__' como una propiedad - no tendría sentido calcular dinámicamente '__slots__' como este . – James

+0

¡Gracias por intentar dar una respuesta! Los resultados finales de este trabajo se pueden ver como http://code.activestate.com/recipes/578152 (threadbox.py). Resuelve el problema de las máquinas tragamonedas. –

0

Me he enfrentado a este error y realmente quería usar ranuras para mis nodos de base de datos personalizados. Aquí está el conjunto de pruebas que he hecho (Su en 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) 

Éstos son los resultados:

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

Como se puede ver, tiene dos opciones:

  1. Defina __slots__ =() para todas menos una de las clases padre,
  2. o haga que uno de los padres pertenezca a la subclase del otro.

en cuenta que debe definir __slots__ en la nueva clase también, de lo contrario se pone un __dict__.

Cuestiones relacionadas