2015-10-27 13 views
9

dire che ho un semplice classe Foo, che proviene da una libreria esterna, quindi non posso cambiare direttamente:impedire l'accesso a una variabile di istanza da sottoclasse, senza influire classe base

class Foo(object): 
    def __init__(self, x): 
     self.x = x 

Voglio creare un sottoclasse Bar e impedire che venga modificato da un'istanza di Bar, ma utilizzare ancora il metodo nei metodi Bar.

Ecco quello che ho provato, ed è probabilmente illuminare l'idea di base, ma purtroppo non funziona:

class Bar(Foo): 

    @property 
    def x(self): 
     return super().x 

    @x.setter 
    def x(self, value): 
     raise NotImplementedError('Do not change x directly, use "do_stuff()" instead') 

    def do_stuff(self, value): 
     if <something>: 
      super().x = value 

Quindi, in pratica ho creato alcune funzioni wrapper (do_stuff()) intorno un attributo, e ora voglio impedire che l'attributo venga modificato direttamente, poiché potrebbe rovinare alcune funzionalità delle funzioni wrapper. È possibile in modo ragionevole?

Modificato con un esempio migliore di ciò che voglio. Non sto cercando di impedire loro di vedere la variabile x, ma invece cambiare dal di fuori di do_stuff()

+0

Quindi non si desidera accedere direttamente alla variabile ma si desidera utilizzare nelle funzioni. L'ho capito correttamente? –

+1

@udhy Sì, esattamente. Voglio impedire * ad altri * di accedere all'attributo dopo aver istanziato 'Bar'. Quindi quando qualcuno fa 'bar = Bar (4); bar.x = 5' genererà un errore e dirà loro di usare 'do_stuff()'. –

+1

'Foo .__ init__' ha lo stesso accesso a' x' tutto ciò che fa. A meno che non si controlli esplicitamente * come * 'x' viene assegnato, ad es. con 'inspect', non vedo come puoi farlo. Siamo tutti adulti consenzienti, quindi bloccare le cose in questo modo non è realmente supportato. – jonrsharpe

risposta

1

Questo dovrebbe essere molto più semplice da realizzare se si è disposti ad evitare del tutto l'ereditarietà:

def main(): 
    bar = Bar(123) 
    bar.fizz() 
    bar.buzz() 
    bar.fizz() 
    bar.set_x(456) 
    print('bar.x =', bar.x) 
    try: 
     bar.x = 123 
    except AttributeError: 
     print('bar.x cannot be set directly') 
    else: 
     raise AssertionError('an AttributeError should have been raised') 
    bar.mutate_x(789) 
    bar.fizz() 
    bar.set_x(0) 
    bar.fizz() 
    bar.mutate_x(1) 
    bar.fizz() 
    bar.set_x('Hello World') 
    bar.fizz() 


class Foo: 

    def __init__(self, x): 
     self.x = x 

    def fizz(self): 
     print(self.x) 

    def buzz(self): 
     self.x = None 


class Bar: 

    def __init__(self, x): 
     self.__foo = foo = Foo(x) 
     self.__copy_methods(foo) 

    def __copy_methods(self, obj): 
     for name in dir(obj): 
      if name.startswith('__') or name.endswith('__'): 
       continue 
      attr = getattr(obj, name) 
      if callable(attr): 
       setattr(self, name, attr) 

    @property 
    def x(self): 
     return self.__foo.x 

    def set_x(self, value): 
     if isinstance(value, int) and value > 0: 
      self.__foo.x = value 

    mutate_x = set_x 

if __name__ == '__main__': 
    main() 
+0

Questa è una soluzione funzionante, ma la classe 'Foo' originale è molto più complicata di quanto abbiate potuto farvelo sapere. Ha una metaclasse personalizzata e un lungo albero ereditario sopra di esso. Non andrò ancora su questa strada (se mai), ma è una soluzione funzionante e terrò presente se non mi viene in mente qualcos'altro! Grazie per l'idea :) –

1

Il risposta breve è: No, questo non è possibile in modo ragionevole.

Il principio guida di Python qui, per usare il fraseggio dal style guide è che siamo tutti utenti responsabili. Significa che il codice è affidabile per non fare cose stupide, e le persone dovrebbero generalmente evitare di fare scherzi con i membri delle classi di altre persone senza una buona ragione.

Il primo e il modo migliore per impedire alle persone di modificare accidentalmente un valore è contrassegnarlo utilizzando il carattere di sottolineatura singolo (_variable). Questo tuttavia potrebbe non offrirti la protezione che desideri contro la modifica accidentale delle tue variabili.

Il passaggio successivo nella protezione consiste nell'utilizzare una doppia sottolineatura. Citando PEP-8:

Per evitare scontri con nome sottoclassi, utilizzare due sottolineature che portano ad invocare il nome di Python mangling regole.

Python altera questi nomi con il nome della classe: se la classe Foo ha un attributo denominato __a, non è possibile accedervi da Foo .__ a. (Un utente insistente potrebbe comunque ottenere l'accesso chiamando Foo._Foo__a.) In genere, è necessario utilizzare doppi underscore iniziali per evitare conflitti di nome con attributi in classi progettate per essere sottoclassate.

Il manegging rende più difficile sovrascrivere accidentalmente un valore.

Ho aggiunto l'enfasi a quest'ultima frase perché è importante. L'utilizzo di questo meccanismo per impedire l'accesso accidentale a un membro non è in realtà il qualcosa che dovrebbe essere fatto per molti membri.

Nel tuo caso specifico, il modo in cui risolverei il problema sarebbe quello di non creare una sottoclasse. Considerare:

class Foo(object): 
    def __init__(self, x): 
     self.x = x 

class Bar(): 
    def __init__(self, x): 
     self._foo = Foo(x) 

    @property 
    def x(self): 
     return self._foo.x 

    def do_stuff(self, value): 
     # Validate the value, and the wrapped object's state 
     if valid: 
      self._foo.x = value 

Naturalmente questo significa che Bar deve avvolgere tutti i metodi s' Foo che si desidera avvolgere. Sì, qualcuno potrebbe ancora,

b = Bar(100) 
b._foo.x = 127 # shame on them :) 

o

b = Bar(100) 
b._foo = EvilFoo(127) 

ma è più difficile da fare involontariamente.

+0

La prima parte della tua risposta è abbastanza irrilevante (beh, almeno inutile per ** me **) poiché, sfortunatamente, non posso cambiare il modo in cui la classe base originale 'Foo' chiama le sue variabili. Ho già risposto a @NoctisSkytower riguardo lo stesso suggerimento che hai fatto (non usando l'ereditarietà): "Questa è una soluzione funzionante, ma la classe Foo originale è molto più complicata di quanto ti abbia fatto sapere. Ha un metaclass personalizzato e un lungo albero ereditario sopra di esso. Non andrò ancora su questa strada (se mai), ma è una soluzione funzionante e terrò a mente se non mi viene in mente qualcos'altro! " –

0

Sei sulla strada giusta, vuoi fare x una proprietà invece di averlo essere un attributo nella sottoclasse. Dove hai sbagliato hai provato a memorizzare i dati grezzi per x su super. Quello che vuoi fare è sfruttare il fatto che la classe genitore può usare la nuova proprietà della sottoclasse in modo trasparente e non ha bisogno di sapere che ora è una proprietà e non un attributo. Qualcosa di simile dovrebbe funzionare per voi: