2011-11-03 6 views
16

Tengo una superclase con un método que llama a otros métodos que solo están definidos en sus subclases. Por eso, cuando creo una instancia de mi superclase y llamo a su método, no puede encontrar el método y genera un error.Prevención de una clase de instanciación directa en Python

Aquí se muestra un ejemplo:

class SuperClass(object): 

    def method_one(self): 
    value = self.subclass_method() 
    print value 


class SubClassOne(SuperClass): 

    def subclass_method(self): 
    return 'subclass 1' 


class SubClassTwo(SuperClass): 

    def subclass_method(self): 
    return 'nubclass 2' 


s1 = SubClassOne() 
s1.method_one() 

s2 = SubClassTwo() 
s2.method_one() 

c = SuperClass() 
c.method_one() 

# Results: 
# subclass 1 
# nubclass 2 
# Traceback (most recent call last): 
# File "abst.py", line 28, in <module> 
#  c.method_one() 
# File "abst.py", line 4, in method_one 
#  value = self.subclass_method() 
# AttributeError: 'SuperClass' object has no attribute 'subclass_method' 

que estaba pensando en cambiar el init de superclase y verificar el tipo de objeto, cuando se crea una nueva instancia. Si el objeto pertenece a la superclase, genera un error. Sin embargo, no estoy muy seguro de si es la manera Pythonic de hacerlo.

¿Alguna recomendación?

+3

La manera Pythonic es escribir buena documentación explicando cómo se usará su clase. – yak

+2

@chown: esto se hace con bastante frecuencia en C++. Lo que está haciendo es esencialmente llamar a métodos virtuales. No hay nada inherentemente malo en ellos, si son la solución adecuada al problema. – rossipedia

+1

Estoy de acuerdo en que no es muy Pythonic – rossipedia

Respuesta

9

Estás hablando de Clases base abstractas, y el lenguaje Python no las admite de forma nativa.

Sin embargo, en la biblioteca estándar, hay un módulo que puede usar para ayudarlo. Consulte la documentación de abc.

+1

En realidad, hay muchas más formas de implementar las suyas en Python, pero solo voy a importar el módulo abc y establecer mi clase como una clase abstracta. Noté que aunque mi clase es abstracta, aún puedo implementar métodos en ella, lo cual es muy bueno. – mohi666

16

Su enfoque es un típico framework pattern.

El uso de __init__ para verificar que type(self) is not SuperClass es una forma razonable de asegurarse de que la SuperClass no se haya instanciado directamente.

El otro enfoque común es proporcionar métodos de código auxiliar que raise NotImplementedError cuando se le llame. Eso es más confiable porque también valida que las subclases hayan anulado los métodos esperados.

7

Esto es lo que podría hacer:

class SuperClass(object): 
    def __init__(self): 
     if type(self) == SuperClass: 
      raise Exception("<SuperClass> must be subclassed.") 
     # assert(type(self) == SuperClass) 

class SubClass(SuperClass): 
    def __init__(self): 
     SuperClass.__init__(self) 

subC = SubClassOne() 
supC = SuperClass() # This line should throw an exception 

cuando se ejecuta (excepción es lanzada!):

[ 18:32 [email protected] ~/so/python ]$ ./preventing-direct-instantiation.py 
Traceback (most recent call last): 
    File "./preventing-direct-instantiation.py", line 15, in <module> 
    supC = SuperClass() 
    File "./preventing-direct-instantiation.py", line 7, in __init__ 
    raise Exception("<SuperClass> must be subclassed.") 
Exception: <SuperClass> must be subclassed. 

Editar (de comentarios):

[ 20:13 [email protected] ~/SO/python ]$ cat preventing-direct-instantiation.py 
#!/usr/bin/python 

class SuperClass(object): 
    def __init__(self): 
     if type(self) == SuperClass: 
      raise Exception("<SuperClass> must be subclassed.") 

class SubClassOne(SuperClass): 
    def __init__(self): 
     SuperClass.__init__(self) 

class SubSubClass(SubClassOne): 
    def __init__(self): 
     SubClassOne.__init__(self) 

class SubClassTwo(SubClassOne, SuperClass): 
    def __init__(self): 
     SubClassOne.__init__(self) 
     SuperClass.__init__(self) 

subC = SubClassOne() 

try: 
    supC = SuperClass() 
except Exception, e: 
    print "FAILED: supC = SuperClass() - %s" % e 
else: 
    print "SUCCESS: supC = SuperClass()" 

try: 
    subSubC = SubSubClass() 
except Exception, e: 
    print "FAILED: subSubC = SubSubClass() - %s" % e 
else: 
    print "SUCCESS: subSubC = SubSubClass()" 

try: 
    subC2 = SubClassTwo() 
except Exception, e: 
    print "FAILED: subC2 = SubClassTwo() - %s" % e 
else: 
    print "SUCCESS: subC2 = SubClassTwo()" 

Impresiones:

[ 20:12 [email protected] ~/SO/python ]$ ./preventing-direct-instantiation.py 
FAILED: supC = SuperClass() - <SuperClass> must be subclassed. 
SUCCESS: subSubC = SubSubClass() 
SUCCESS: subC2 = SubClassTwo() 
+0

No digo que esto no sea muy bueno, pero creo que muestra exactamente por qué no deberías intentar hacer este tipo de cosas. Estoy bastante seguro de que ambos ejemplos fallan con herencia múltiple. – sdolan

+0

@sdolan Acabo de probar, y realmente funcionó con 'clase SubSubClass (SubClassOne):' y llamando a 'SubClassOne .__ init __ (self)' de su '__init__'! – chown

+0

Ahh muy genial. Gracias por hacer esa actualización. – sdolan

22

Anularía __new__() en la clase base y simplemente no crearía ninguna instancia si se trata de la clase base.

class BaseClass(object): 

    def __new__(cls, *args, **kwargs): 
     if cls is BaseClass: 
      raise TypeError("base class may not be instantiated") 
     return object.__new__(cls, *args, **kwargs) 

Esto separa las preocupaciones un poco mejor que tenerlo en __init__(), y "falla rápido."

+2

El módulo 'abc' es probablemente adecuado para el 90% de los casos, pero esta técnica es útil en algunos otros. Algunos que he encontrado: 1.) quieres una clase mixin reutilizable pero en realidad no tiene métodos abstractos, por lo que 'ABCMeta' no impedirá que se cree una instancia; 2.) estás definiendo una clase "base" o mixin que en realidad subclasifica algo más, pero no quieres que sea directamente instanciable. – ches

+0

Esto es de hecho muy útil. Me gustaría agregar que si BaseClass heredó VeryBaseClass, entonces devuelva VeryBaseClass .__ new __() en lugar de object .__ new __() – cfchou

Cuestiones relacionadas