Quanto tempo fa un tipo hashable deve diventare immutabile?

0

Supponiamo di avere una classe che sembra vagamente simile a questa:

class Foo(object):
    _value = None  # XXX: see below

    def __init__(self, value):
        self._value = value

    @property
    def value(self):
        """The value of this Foo."""
        return self._value

    def __hash__(self):
        return hash(self.value)

    def __eq__(self, other):
        return type(self) is type(other) and self.value == other.value

(So che la riga _value = None è sbagliata, nel mio codice attuale _value è un descrittore di dati con un valore predefinito. Non aumenta mai AttributeError anche se non è stato ancora impostato, e I ' Sto solo cercando di emulare quel comportamento nel minor numero possibile di righe di codice. Potrei eliminare il comportamento, ma complicherebbe in modo significativo le cose in altre aree della mia base di codice.)

Ho violato le regole? Devo impostare _value in Foo.__new__() ? Chiaramente deve essere impostato da qualche parte; ci sarà un punto nel tempo in cui cambia il hash() della nostra istanza. Quando dovrebbe accadere, esattamente?

Se c'è non qualcosa di sbagliato con il codice sopra, allora perché il seguente codice è sbagliato? Logicamente, questa combinazione è illegale, ma non sono sicuro di quale classe abbia infranto le regole:

bar = set()

class Baz(object):
    def __init__(self, *args, **kwargs):
        bar.add(self)
        super(Baz, self).__init__(*args, **kwargs)  # play nice with MI

class Qux(Baz, Foo):
    pass

assert Qux('quuux') in bar  # assertion fires because the hash() changed
    
posta Kevin 19.12.2014 - 03:00
fonte

2 risposte

3

Direi che Baz ha infranto le regole. Usando self come hash prima di invocare anche altri metodi __init__ nella MRO, si presume che quei metodi non influiscano sull'hash. Questa è una supposizione sbagliata. Sebbene tecnicamente sia più sicuro costruire classi immutabili in modo che __new__ imposti tutto lo stato e lo renda completamente, assolutamente, immutabile (come tuple e namedtuple ), questo è spesso molto goffo e non necessario.

L'impostazione di alcune variabili private che sono di sola lettura per il mondo esterno e mai mutate in alcun metodo (diverso da __init__ in cui sono impostate per la prima volta) è un modo perfettamente valido e comune da implementare classi immutabili.

Questo si collega a un punto più generale: fare qualsiasi cosa con un oggetto mentre è ancora in fase di inizializzazione è molto rischioso e deve essere pensato molto bene. Molti invarianti possono essere violati prima che __init__ abbia finito, quindi la maggior parte del codice non può interagire in modo significativo con un oggetto parzialmente inizializzato. Fortunatamente, un'implementazione sana di __new__ fa in modo che il nuovo oggetto venga prima passato a __init__ (e, naturalmente, qualsiasi codice che __init__ richiama se stesso - che, di nuovo, deve essere scelto con cura).

    
risposta data 19.12.2014 - 12:24
fonte
1

L'istanza dovrebbe essere immutabile nel momento in cui viene utilizzata come chiave in un dettato. Prima di ciò, può mutare. Ma preferibilmente dovrebbe essere incapace di mutazione dopo il momento della costruzione, solo per evitare errori di programmazione.

Lo schema builder si adatta bene: il passo finale .build() restituisce un oggetto immutabile. Puoi inoltre impedire la modifica accidentale degli attributi impostando __setattr__ dell'istanza su qualcosa che genera un'eccezione.

Sfortunatamente, non hai fornito un minimo esempio eseguibile del problema nel secondo frammento di codice. La prossima volta ti preghiamo di . Posso solo sospettare che da qualche parte nel vero codice problematico tu confondi self dell'oggetto originale con qualcos'altro. (L'idea di aggiornare una tabella hash globale da un costruttore non mi sembra particolarmente elegante, anche se memoizing decorators di solito lo fanno in modo affidabile.)

    
risposta data 19.12.2014 - 04:08
fonte

Leggi altre domande sui tag