2009-05-04 15 views
16

Voglio creare un oggetto in python che ha alcuni attributi e voglio proteggermi dall'usare accidentalmente il nome dell'attributo sbagliato. Il codice è il seguente:python, __slots__ e "l'attributo è di sola lettura"

class MyClass(object) : 
    m = None # my attribute 
    __slots__ = ("m") # ensure that object has no _m etc 

a = MyClass() # create one 
a.m = "?" # here is a PROBLEM 

Ma dopo l'esecuzione di questo semplice codice, ottengo uno strano errore:

Traceback (most recent call last): 
    File "test.py", line 8, in <module> 
    a.m = "?" 
AttributeError: 'test' object attribute 'm' is read-only 

C'è qualche programmatore saggio che può risparmiare un po 'del loro tempo e mi illumini sugli errori di "sola lettura"?

+1

Questo sembra un caso d'uso inutile. Perché stai facendo questo? Perché non stai usando le proprietà? –

+0

Voglio proff di scrivere un nome di proprietà con un errore di battitura, per eample se scrivo object.my_prperty = 1 invece di object.my_property = 1 (mancata 'o'), python non mostrerà errori, ma avrò un errore logico nel mio programma. Quindi voglio limitare una proprietà al mio set predefinito, quindi solo loro possono essere ammessi. Come posso risolvere questo con le proprietà? – grigoryvp

+9

generalmente in python accettiamo la possibilità di errori di ortografia negli incarichi e scriviamo buoni test che catturerebbero anche errori superficiali e più semantici. Umilmente suggerisco che se vuoi scrivere python, dovresti considerare di farlo in modo pitone invece di combattere la lingua con le unghie e con i denti. Aggiungo che gli errori ortografici nel mio codice Python mi sono costati * forse * 20 minuti di tempo negli ultimi 9 anni. – llimllib

risposta

38

Quando si dichiara variabili di istanza utilizzando __slots__, Python crea un descriptor object come una variabile di classe con lo stesso nome. Nel tuo caso, questo descrittore viene sovrascritto dalla variabile di classe m che si sta definendo al seguente riga:

m = None # my attribute 

Ecco cosa dovete fare: Non definire una variabile di classe denominata m, e inizializzare l'istanza variabile m nel metodo __init__.

Come nota a margine, le tuple con singoli elementi richiedono una virgola dopo l'elemento. Entrambi funzionano nel codice perché __slots__ accetta una singola stringa o una sequenza/iterabile di stringhe. In generale, per definire una tupla contenente l'elemento 1, utilizzare (1,) o 1, e non (1).

+0

Sai perchè se ho scritto: __slots__ = [ "m"] m = 5 Python sovrascriverà oggetto descrittore con variabile e non userà il metodo set() degli oggetti descrittori per impostare invece il valore? – grigoryvp

+7

Perché m in questo contesto è una variabile di classe, non una variabile di istanza. Per utilizzare il metodo set() del descrittore, dovresti assegnare al m di un oggetto, non a una classe. Se si desidera inizializzare una variabile di istanza di un oggetto, farlo all'interno del metodo __init__ come ho fatto nel mio snippet di codice. –

+0

La chiave dai documenti (che btw spostato [qui] (https://docs.python.org/2/reference/datamodel.html#slots)) è '__slots__ sono implementate a livello di classe creando descrittori per ogni variabile nome. Di conseguenza, non è possibile utilizzare gli attributi di classe per impostare valori predefiniti per le variabili di istanza definite da __slots__; altrimenti, l'attributo class sovrascrivere l'assegnazione del descrittore'. Quindi, quello che succede è che (indipendentemente dall'ordine di definire 'm' e' __slots__'?) Quando viene tentato l'assegnamento il metodo set non è più mappato a nulla - né viene ottenuto ma torna a MyClass.m –

7

__slots__ funziona con variabili di istanza, mentre quello che hai è una variabile di classe. Questo è il modo che si dovrebbe fare è:

class MyClass(object) : 
    __slots__ = ("m",) 
    def __init__(self): 
    self.m = None 

a = MyClass() 
a.m = "?"  # No error 
6

Considerate questo.

class SuperSafe(object): 
    allowed= ("this", "that") 
    def __init__(self): 
     self.this= None 
     self.that= None 
    def __setattr__(self, attr, value): 
     if attr not in self.allowed: 
      raise Exception("No such attribute: %s" % (attr,)) 
     super(SuperSafe, self).__setattr__(attr, value) 

Un approccio migliore consiste nell'utilizzare i test di unità per questo tipo di controllo. Questa è una quantità ragionevole di spese generali di runtime.

+4

+1 per non abusando __slots__ – Ravi

+3

Ma questo è più linee di codice rispetto a __slots__? Perché controllare manualmente ciò che può essere eseguito automaticamente dalla lingua. – grigoryvp

+3

Per evitare messaggi di "errore molto strano" e per evitare di dover trascorrere molto tempo a eseguire il debug dei misteri interni di __slots__. Preferisco soluzioni ovvie e semplici in cui non è richiesta una conoscenza segreta per eseguire il debug. –

14

Si sta abusando completamente di __slots__. Impedisce la creazione di __dict__ per le istanze. Questo ha senso solo se si incontrano problemi di memoria con molti piccoli oggetti, perché l'eliminazione di __dict__ può ridurre l'ingombro. Questa è una ottimizzazione hardcore che non è necessaria nel 99,9% di tutti i casi.

Se hai bisogno del tipo di sicurezza che hai descritto allora Python è davvero la lingua sbagliata. Meglio usare qualcosa di rigido come Java (invece di provare a scrivere Java in Python).

Se non riuscissi a capire da solo perché gli attributi di classe hanno causato questi problemi nel tuo codice, allora forse dovresti riflettere due volte sull'introduzione di hack linguistici come questo. Probabilmente sarebbe più saggio familiarizzare prima con la lingua.

Solo per completezza, ecco lo documentation link for slots.

+4

+1 per completezza. Come già detto, __slots__ non è una cattiva idea, ma richiede un uso attento. È più di un controllo di sicurezza e aggiunge molta complessità all'implementazione che richiederà di prestare attenzione a come si usa l'oggetto. –

0
class MyClass(object) : 
    m = None # my attribute 

Il m qui è gli attributi di classe, piuttosto che l'attributo di istanza. È necessario collegarlo autonomamente con la propria istanza in __init__.