Statement of Purpose
This is a suggestion to support a new dunder method that tests for equivalence, but not necessarily equality, between two objects. The corresponding infix operator would be ~.
Example Use Case
Suppose I am defining a class to represent metric units, which users will initialize with a string representation of a given unit (e.g., 'meter', 'J / s', etc.). Further suppose that I want two instances to be equal if and only if their strings represent the same unit, but I want two unequal instances to be equivalent if their strings represent equivalent units. Following this logic 'N' and 'newton' would be equal while 'N' and 'kg * m / s^2' would be equivalent but not equal.
Here is a minimal implementation of the class:
class Unit:
def __init__(self, arg: str) -> None:
self.arg = arg
def __equal__(self, other) -> bool:
if isinstance(other, Unit):
return self.arg == other.arg
# ... more complex logic for cases like 'm' == 'meter'
try:
return self.arg == str(other)
except TypeError:
return False
def __equiv__(self, other) -> bool:
if isinstance(other, Unit):
# ... implementation of metric equivalence
if isinstance(other, str):
# ... possibly alternate implementation of metric equivalence
return False
Here are some usage examples
>>> Unit('newton') == Unit('newton')
True
>>> Unit('newton') == Unit('N')
True
>>> Unit('newton') == Unit('kg * m / s^2')
False
>>> Unit('newton') ~ Unit('kg * m / s^2')
True
Possible Workarounds
It would certainly be possible to define a function that checks equivalence, as defined by the user, between two objects. It would also be possible to define an instance method or a class method to restrict the scope according to user needs. However, the proposed dunder method and corresponding infix operator would simplify and standardize these use cases.
Relationship to Notational Conventions
The symbol ~, when used between two operands, can have one of multiple meanings. It may be used, formally or informally, to indicate that
- an equivalence relation exists between two sets
- two functions are asymptotically equivalent
- two triangles are similar
- a stochastic variable has a given probability distribution or that two stochastic variables have the same probability distribution
- two graph nodes are adjacent to each other (i.e., are connected by an edge)
- the numerical value of a physical quantity has a given order of magnitude or two physical quantities have the same order of magnitude
- two numbers are approximately equal (e.g., a \sim b to mean a \approx b)
- a quantity is proportional to another quantity (e.g., y \sim x^2 to mean y \propto x^2)
(cf. Wikipediaâs Glossary of Mathematical Symbols, Wikipedia/Tilde, and MathWorld/Tilde)
Of these uses
- 1, 2, 3, and 4 represent an explicit or implicit notion of rigorous equivalence between two objects, and thus support the proposed use of
~; - 5 represents a formal notion of connection independent of equivalence (or equality) that could lead to potential confusion;
- 6 represents an informal notion of approximation that is weaker than âapproximately equal toâ;
- 7 and 8 are abuses of notation.
Similarity to Existing Operators
The token ~ is already in use as a unary operator, which may be implemented on objects by defining __invert__. However, this should not cause confusion, since + and - are each used for a unary (corresponding to __pos__ and __neg__) and a binary operator (corresponding to __add__ and __sub__).
Similarity to Previous Proposals
I could not find any similar previous Python Ideas proposals by searching for â__equiv__â or âequivalenceâ, and internet searches for âpython equivalence operatorâ (and variations) only turned up discussions about equality versus identity. Of course, I would love to know if I missed something!