2011-01-25 6 views
5

Abstract base classes can still be handy in Python. Nello scrivere una classe base astratta in cui voglio che ogni sottoclasse di avere, per esempio, un metodo spam(), voglio scrivere qualcosa del genere:di Python(), classi base astratte, e NotImplementedError

class Abstract(object): 
    def spam(self): 
     raise NotImplementedError 

La sfida arriva anche nel voler usare super(), e per farlo correttamente includendolo nell'intera catena di sottoclassi. In questo caso, a quanto pare devo per avvolgere ogni super chiamata come la seguente:

class Useful(Abstract): 
    def spam(self): 
     try: 
      super(Useful, self).spam() 
     except NotImplementedError, e: 
      pass 
     print("It's okay.") 

va bene per un semplice sottoclasse, ma quando si scrive una classe che ha molti metodi, il try-tranne che cosa diventa un po 'ingombrante e un po 'brutto. Esiste un modo più elegante di sottoclassi delle classi base astratte? Sto solo facendo qualcosa di sbagliato?

+5

che rende molto poco senso. Dovresti sapere quali metodi di superclasse sono implementati (per i quali 'super 'ha senso) e quali non sono implementati perché sono astratti. Puoi leggere la fonte. –

+0

'raise SyntaxError' è anche nella lingua. La domanda è "perché scrivere tutto quel codice quando la semplice ispezione della classe astratta può salvarti scrivendo tutto quel codice"? –

+0

@ S.Lott Ah, capito ora. Dovresti sottometterlo come risposta, a proposito, perché lo è. – gotgenes

risposta

8

È possibile farlo in modo pulito in Python 2.6 + con la abc module:

import abc 
class B(object): 
    __metaclass__ = abc.ABCMeta 
    @abc.abstractmethod 
    def foo(self): 
     print 'In B' 

class C(B): 
    def foo(self): 
     super(C, self).foo() 
     print 'In C' 

C().foo() 

L'uscita sarà

In B 
In C 
+0

Questo non è necessariamente lo stesso - non è possibile istanziare B dal tuo esempio. –

+0

Interessante! Non avevo nemmeno preso l'aggiunta del modulo 'abc'! – gotgenes

+0

@Tomasz Anche interessante. Quali sono le implicazioni per i test unitari, quindi, se non è possibile istanziare la classe? – gotgenes

7

Non scrivere tutto quel codice. L'ispezione semplice della classe astratta può farti risparmiare tutto ciò che scrivi.

Se il metodo è astratto, la sottoclasse concreta non chiama super.

Se il metodo è concreto, la sottoclasse concreta chiama super.

+1

Questa è una soluzione semplice a meno che non si stia cercando un diverso tipo di eredità cooperativa. vale a dire. L'abstract è A. A <- B, ma poi vuoi supportare l'inserimento di C così A <- C <- B. B non chiama super, quindi non funziona correttamente. Penso che il punto principale sia che quando usi Super devi essere consapevole dell'ereditarietà cooperativa e dei tuoi obiettivi per la tua specifica gerarchia di classi. Vedi la mia risposta per l'implementazione del mio esempio sopra. –

3

Il punto chiave per comprendere questo è super() è per l'implementazione dell'ereditarietà cooperativa. Il modo in cui le classi cooperano dipende da te dal programmatore. super() non è magico e non sa esattamente quello che vuoi! Non ha molto senso usare super per una gerarchia piatta che non ha bisogno di ereditarietà cooperativa, quindi in quel caso il suggerimento di S. Lott è azzeccato. Sottoclassi di maggio utile o non può decidere di utilizzare super() a seconda dei loro obiettivi :)

Per esempio: L'estratto è A. A < - B, ma poi si vuole sostenere l'inserimento di C in questo modo Un < - C < - B .

class A(object):                       
    """I am an abstract abstraction :)""" 
    def foo(self): 
     raise NotImplementedError('I need to be implemented!') 

class B(A): 
    """I want to implement A""" 
    def foo(self): 
     print('B: foo') 
     # MRO Stops here, unless super is not A 
     position = self.__class__.__mro__.index 
     if not position(B) + 1 == position(A): 
      super().foo() 

b = B()  
b.foo() 

class C(A): 
    """I want to modify B and all its siblings (see below)""" 
    def foo(self): 
     print('C: foo') 
     # MRO Stops here, unless super is not A 
     position = self.__class__.__mro__.index 
     if not position(C) + 1 == position(A): 
      super().foo() 

print('') 
print('B: Old __base__ and __mro__:\n') 
print('Base:', B.__bases__) 
print('MRO:', B.__mro__) 
print('') 
# __mro__ change implementation 
B.__bases__ = (C,) 
print('B: New __base__ and __mro__:\n') 
print('Base:', B.__bases__) 
print('MRO:', B.__mro__) 
print('') 
b.foo() 

E l'output:

B: foo 

B: Old __base__ and __mro__: 

Base: (<class '__main__.A'>,) 
MRO: (<class '__main__.B'>, <class '__main__.A'>, <class 'object'>) 

B: New __base__ and __mro__: 

Base: (<class '__main__.C'>,) 
MRO: (<class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>) 

B: foo 
C: foo