No way to make instances of different classes equal without rewriting `__eq__()` entirely

Aperta
#1,500 1 commento 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

Valutazione

Difficoltà
5/5
Tempo stimato
Più di una settimana
Idoneità per principianti
25/100
Tipo di issue
Funzionalità
Chiarezza
Da chiarire
Stato di attività
Ferma
Stack tecnologico
python
Ambito
tooling

Direzione di ricerca

The issue names no source file or test entry point. Start by reviewing attrs' generated eq behavior and the proposed API alternatives, then establish a concrete, backwards-compatible scope; done should include agreed cross-class equality semantics for mutable and frozen attrs classes and coverage for the relevant field comparisons.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Descrizione

Motivation and Problem

In my code, it's a common idiom to have a two classes called, e.g., SomethingMutable and SomethingFrozen; these are both attrs classes decorated with @attrs.define and @attrs.frozen, respectively. Their field definitions are "the same" with minor type differences to account for mutability (list vs. tuple, set vs. frozenset, etc.)

I'd like instances of SomethingMutable and SomethingFrozen to compare equal IFF every the values for the defined fields are all equal.

What doesn't work

The attrs-generated __eq__() always checks instance types are the same before checking that the field values match, meaning that instances of different attrs classes can never compare equal. Consequently, overriding __eq__() and calling super() isn't helpful.

There's no obvious way to leverage the attrs-generated, field-by-field comparisons currently in __eq__() unless both instances are exactly the same type.

Possible approaches

It would be very helpful if there were a way to tell attrs to ignore the classes when comparing, and consider only those fields defined by the left instance's class.

This could take a number of forms, such as:

  • __eq__() could take an optional keyword argument ignore_type: bool, intended for use via super()
  • attrs could break __eq__() into two steps: one to check instance types, and one to check field values
    • classmethod[__attrs_types_equal__(cls, other: type) -> bool]
      would replace today's __eq__()'s first step, i.e., roughly: if type(other) is not type(self): return False
    • __attrs_fields_equal__(self, other: Any) -> bool
      would check that every field in self is matched by an attribute of other which has an equal value (or whatever attrs.field(eq=...) says to do)
      • for example
        • if class Child(Parent): ... adds at least one field not found in Parent
        • and given: c: Child; p: Parent
        • then it would typically be true that p.__attrs_fields_equal__(c),
          • since p._afe_(c) considers only fields in p, all of which are also present in c (per the LSP/Liskov Substitution Principle),
        • ... but typically false that c.__attrs_fields_equal__(p)
          • since c._afe_(p) considers only fields in c, some of which are absent from p (by assumption)
    • __eq__() could be replaced by just a few, static lines:
      return (
              self.__attrs_types_equal__(type(other))  and
              self.__attrs_fields_equal__(other)
              )
      
      • breaking up __eq__() might carry a performance penalty
        • but I think it should usually be possible to detect cases where a bifurcated implementation is called for, add an override flag to attrs.define() (etc.) for edge cases, and use a unified implementation (as we do today) where we can
        • __eq__() could also check whether type(self).__attrs_types_equal__ is the unmodified attrs-provided version (identity comparison); and, if so, use an inlined copy of the same generated code to save the overhead of the method call
          • (...and similarly for __attrs_fields_equal__())
          • this should be safe even even where there's monkeypatching
Lingua principale
Python
Stelle
5.8k
Fork
480
Merge medio
2h 15m
PR unite (30g)
2

Guida per i contributori

Apri la guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Altre issue di python-attrs/attrs

Tutte le issue di python-attrs/attrs

Issue simili

Altre issue su Python

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.