Skip to content

Commit

Permalink
Apply suggestions from code review
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexWaygood authored Oct 26, 2024
1 parent 820d293 commit c2c1774
Show file tree
Hide file tree
Showing 2 changed files with 23 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
# Comparison: Membership Test

In Python, "membership test operators" refer to `in` and `not in` operator. To
customize their behavior, classes can implement methods like `__contains__`,
`__iter__`, or `__getitem__`.
In Python, the term "membership test operators" refers to the operators
`in` and `not in`. To customize their behavior, classes can implement one of
the special methods `__contains__`, `__iter__`, or `__getitem__`.

For references, see:

- <https://docs.python.org/3/reference/expressions.html#membership-test-details>
- <https://docs.python.org/3/reference/datamodel.html#object.__contains__>
- <https://snarky.ca/unravelling-membership-testing/>

## Implements `__contains__`

Expand Down Expand Up @@ -47,10 +48,11 @@ reveal_type(42 not in A()) # revealed: bool

## Implements `__getitems__`

The final fallback is to implement `__getitem__` for integer keys: Python will
call it with 0, 1, 2... until it either finds the needle (returning True for the
membership test) or `__getitem__` raises IndexError, which is silenced and
returns `False` for the membership test.
The final fallback is to implement `__getitem__` for integer keys. Python will
call `__getitem__` with `0`, `1`, `2`... until either the needle is found
(leading the membership test to evaluate to `True`) or `__getitem__` raises
`IndexError` (the raised exception is swallowed, but results in the membership
test evaluating to `False`).

```py
class A:
Expand All @@ -65,7 +67,7 @@ reveal_type(42 not in A()) # revealed: bool

## Wrong Return Type

Python coerces the results of containment checks to bool, even if `__contains__`
Python coerces the results of containment checks to `bool`, even if `__contains__`
returns a non-bool:

```py
Expand Down Expand Up @@ -149,7 +151,7 @@ reveal_type(CheckGetItem() in B()) # revealed: bool
## Invalid Old-Style Iteration

If `__getitem__` is implemented but does not accept integer arguments, then
membership test is not supported and should emit a diagnostic.
the membership test is not supported and should trigger a diagnostic.

```py
class A:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ For references, see:
- <https://docs.python.org/3/reference/datamodel.html#object.__lt__>
- <https://snarky.ca/unravelling-rich-comparison-operators/>

## Implements Rich Comparison Dunders For Same Class
## Rich Comparison Dunder Implementations For Same Class

Classes can support rich comparison by implementing dunder methods like
`__eq__`, `__ne__`, etc. The most common case involves implementing these
Expand Down Expand Up @@ -45,7 +45,7 @@ reveal_type(A() > A()) # revealed: list
reveal_type(A() >= A()) # revealed: set
```

## Implements Rich Comparison Dunders for Other Class
## Rich Comparison Dunder Implementations for Other Class

In some cases, classes may implement rich comparison dunder methods for
comparisons with a different type:
Expand Down Expand Up @@ -85,8 +85,8 @@ reveal_type(A() >= B()) # revealed: set
## Reflected Comparisons

Fallback to the right-hand side’s comparison methods occurs when the left-hand
side does not define them. Note: class B has its own `__eq__` and `__ne__`
methods to override those of object, but these methods will be ignored here
side does not define them. Note: class `B` has its own `__eq__` and `__ne__`
methods to override those of `object`, but these methods will be ignored here
because they require a mismatched operand type.

```py
Expand Down Expand Up @@ -119,7 +119,12 @@ class B:
def __ne__(self, other: str) -> B:
return B()

# TODO: should be `int` and `float`, need to check arg type and fall back to `rhs.__eq__` and `rhs.__ne__`. Because `object.__eq__` and `object.__ne__` accept `object` in typeshed, this can only happen with an invalid override of these methods, but we still support it.
# TODO: should be `int` and `float`.
# Need to check arg type and fall back to `rhs.__eq__` and `rhs.__ne__`.
#
# Because `object.__eq__` and `object.__ne__` accept `object` in typeshed,
# this can only happen with an invalid override of these methods,
# but we still support it.
reveal_type(B() == A()) # revealed: B
reveal_type(B() != A()) # revealed: B

Expand All @@ -143,8 +148,8 @@ reveal_type(C() <= C()) # revealed: float
## Reflected Comparisons with Subclasses

When subclasses override comparison methods, these overridden methods take
precedence over those in the parent class. Class B inherits from A and redefines
comparison methods to return types other than A.
precedence over those in the parent class. Class `B` inherits from `A` and
redefines comparison methods to return types other than `A`.

```py
from __future__ import annotations
Expand Down

0 comments on commit c2c1774

Please sign in to comment.