2010-05-19 18 views
7

No veo lo que espero cuando uso ABCMeta y abstractmethod.Python 2.6, 3 malentendido de clase base abstracta

Esto funciona bien en python3:

from abc import ABCMeta, abstractmethod 

class Super(metaclass=ABCMeta): 
    @abstractmethod 
    def method(self): 
     pass 

a = Super() 
TypeError: Can't instantiate abstract class Super ... 

Y en 2.6:

class Super(): 
    __metaclass__ = ABCMeta 
    @abstractmethod 
    def method(self): 
     pass 

a = Super() 
TypeError: Can't instantiate abstract class Super ... 

Ambos también funcionan bien (me da la excepción esperada) si derivo Súper del objeto, además de ABCMeta.

Ambos "fallan" (sin excepción) si derivo Super de la lista.

Quiero que una clase base abstracta sea una lista, pero abstracta, y concreta en sub clases.

¿Lo estoy haciendo mal, o no debería querer esto en Python?

+1

¿Qué intenta exactamente lograr con esto? Un ejemplo concreto realmente haría mucho por aquí. –

+0

clases base abstractas son la desiderata de una mente envenenada o_O http://stackoverflow.com/questions/372042/difference-between-abstract-class-and-interface-in-python – msw

+0

musicfreak: estoy tratando de lograr una lista basada en la clase base abstracta que define una interfaz para sus clases derivadas, y cuya clase base no puede ser instanciada. Quiero poder usar las clases derivadas como una lista porque esa es la naturaleza de mis objetos, con bits adicionales de interfaz porque mis objetos son mucho más especiales. :) msw: ¿Por qué desiderata ("algo deseado como esencial" de una mente envenenada? Supongo que estás diciendo que no debería querer esto (en python), pero ¿por qué? ¿Qué es un mecanismo más pitónico? – Aaron

Respuesta

14

Con Super construcción como en sus fragmentos de trabajo, lo que está llamando cuando lo hace es Super():

>>> Super.__init__ 
<slot wrapper '__init__' of 'object' objects> 

If Super hereda de list, llaman Superlist:

>>> Superlist.__init__ 
<slot wrapper '__init__' of 'list' objects> 

Ahora, las clases base abstractas están destinadas a ser utilizables como mixin clases, para ser heredadas de forma múltiple (para obtener el patrón de diseño "Método de plantilla" es que un ABC puede ofrecer) junto con una clase concreta, sin hacer que el descendiente resultante sea abstracto. Así que considere:

>>> class Listsuper(Super, list): pass 
... 
>>> Listsuper.__init__ 
<slot wrapper '__init__' of 'list' objects> 

¿Vea el problema? Según las reglas de herencia múltiple llamando al Listsuper() (que es no permitido que falla solo porque hay un método abstracto colgante) ejecuta el mismo código que llamando al Superlist() (que le gustaría fallar). Ese código, en la práctica (list.__init__), hace no se opone a los métodos abstractos colgantes - solo object.__init__ hace. Y solucionar eso probablemente rompería el código que depende del comportamiento actual.

La solución sugerida es: si desea una clase base abstracta, todas sus bases deben ser abstractas. Entonces, en lugar de tener concreto list entre sus bases, use como base collections.MutableSequence, agregue un __init__ que hace un atributo ._list, e implemente los métodos abstractos de MutableSequence por delegación directa al self._list. No es perfecto, pero tampoco es tan doloroso.

+0

(Creo que eché a perder mi comentario.) Alex, gracias, esto fue claro y al grano. Tengo algunas lecturas para hacer ahora. – Aaron

1

En realidad, el problema es con __new__, en lugar de con __init__. Ejemplo:

from abc import ABCMeta, abstractmethod 
from collections import OrderedDict 

class Foo(metaclass=ABCMeta): 
    @abstractmethod 
    def foo(self): 
     return 42 

class Empty: 
    def __init__(self): 
     pass 

class C1(Empty, Foo): pass 
class C2(OrderedDict, Foo): pass 

C1() falla con un TypeError como se esperaba, mientras que C2.foo() rendimientos 42.

>>> C1.__init__ 
<function Empty.__init__ at 0x7fa9a6c01400> 

Como se puede ver, no es usando object.__init__ ni es siquiera invocando su superclase (object) __init__

puede verificar llamando __new__ mismo:

C2.__new__(C2) funciona bien, mientras Obtendrá el TypeError habitual con C1.__new__(C1)

Por lo tanto, no es tan claro como

si desea una clase base abstracta, todas sus bases deben ser abstractas.

Mientras que eso es una buena sugerencia, lo contrario no es necesariamente cierto: ni OrderedDict ni Empty son abstractas, y sin embargo subclase de que el primero es "concreto", mientras que el último es "abstracto"

Si' se pregunta, he utilizado OrderedDict en el ejemplo en vez de list ya que este último es un tipo "built-in", y por lo tanto no se puede hacer:

OrderedDict.bar = lambda self: 42 

y quería hacerlo explícito que el problema no está relacionado lo.