2011-11-03 5 views
16

Ho una super classe con un metodo che chiama altri metodi che sono definiti solo nelle sue sottoclassi. Ecco perché, quando creo un'istanza della mia super classe e chiamo il suo metodo, non riesce a trovare il metodo e genera un errore.Prevenzione di una classe dall'istanza diretta in Python

Ecco un esempio:

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' 

stavo pensando di cambiare il init di classe super e verificare il tipo di oggetto, quando viene creata una nuova istanza. Se l'oggetto appartiene alla super classe, genera un errore. Tuttavia, non sono sicuro se sia il modo Pythoniano di farlo.

Qualche consiglio?

+3

Il modo Pythonic è di scrivere una buona documentazione che spiega come deve essere utilizzata la classe. – yak

+2

@chown: in realtà viene eseguito abbastanza frequentemente in C++. Quello che sta facendo è essenzialmente chiamare metodi virtuali. Non c'è nulla di intrinsecamente sbagliato in loro, se sono la soluzione adeguata al problema. – rossipedia

+1

Concordo sul fatto che non è molto Pythonic – rossipedia

risposta

9

Stai parlando di Base Base astratta e il linguaggio Python non li supporta in modo nativo.

Tuttavia, nella libreria standard è disponibile un modulo che può essere d'aiuto. Controlla la documentazione di abc.

+1

In realtà ci sono molti più modi per implementare il suo in Python, ma ho intenzione di importare semplicemente il modulo abc e impostare la mia classe come una classe astratta. Ho notato che anche se la mia classe è astratta, posso ancora implementare dei metodi, il che è molto bello. – mohi666

16

L'approccio è un tipico framework pattern.

Utilizzare __init__ per verificare che type(self) is not SuperClass sia un modo ragionevole per assicurarsi che la SuperClass non sia stata creata direttamente.

L'altro approccio comune è quello di fornire metodi di stub che vengono chiamati raise NotImplementedError. Ciò è più affidabile perché convalida anche che le sottoclassi hanno sovrascritto i metodi previsti.

7

Questo è quello che potrei fare:

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 

Quando run (viene generata un'eccezione!):

[ 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. 

Edit (dai commenti):

[ 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()" 

Stampe:

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

Non dire che questo non è bello, ma penso che mostri esattamente il motivo per cui non dovresti provare a fare questo genere di cose. Sono abbastanza sicuro che entrambi gli esempi falliscono con l'ereditarietà multipla. – sdolan

+0

@sdolan Ho appena testato, e in realtà ha funzionato con 'class SubSubClass (SubClassOne):' e chiamando 'SubClassOne .__ init __ (self)' dal suo '__init__'! – chown

+0

Ahh molto bello. Grazie per aver effettuato questo aggiornamento. – sdolan

22

Ignorerei __new__() nella classe base e semplicemente non riuscirò a istanziare affatto se è la classe 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) 

Questo separa riguarda un po 'meglio averlo in __init__(), e "fallisce veloce".

+2

Il modulo 'abc' ha probabilmente ragione per il 90% dei casi, ma questa tecnica è utile in alcuni altri. Alcuni che ho incontrato: 1.) vuoi una classe mixin riutilizzabile ma in realtà non ha metodi astratti, quindi 'ABCMeta' non impedirà che venga istanziata; 2.) stai definendo una classe "base" o un mixin che in realtà sottoclasse qualcos'altro, ma non vuoi che sia direttamente istanziabile. – ches

+0

Questo è davvero molto utile. Vorrei aggiungere che se BaseClass ha ereditato VeryBaseClass, restituisce VeryBaseClass .__ new __() invece di object .__ new __() – cfchou