Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Include documentation on how this works #38

Closed
Marco-Kaulea opened this issue May 9, 2022 · 3 comments · Fixed by #41
Closed

Include documentation on how this works #38

Marco-Kaulea opened this issue May 9, 2022 · 3 comments · Fixed by #41

Comments

@Marco-Kaulea
Copy link
Contributor

I find this functionality quite interesting and think it could be useful.
But I'm a bit hesitant to include a library with "magical" behavior that I don't understand.

My main question is, why is the __eq__ method of the right operand used?
The python documentation seems to suggest that x == y should call x.__eq__(y).

If the operands are of different types, and right operand’s type is a direct or indirect subclass of the left operand’s type, the reflected method of the right operand has priority, otherwise the left operand’s method has priority.

The last paragraph in the docs above point me to the __eq__ implementation in the DirtyEqualsMeta class. But I'm not sure what is going on.

@samuelcolvin
Copy link
Owner

My guess would be that the docs are slightly out of date and in reality either:

  • the runtime looks to check if either value has a custom __eq__ method, and if one does, uses that
  • or, the __eq__ method for all the types used here check for a custom __eq__ method on other and use that if it exists and is not the default

Honestly I haven't looked into it in more detail, but I would guess it's more than just an implementation detail since it also works with pypy.

@samuelcolvin
Copy link
Owner

If you feel like digging into the source and working out what's going on, I'd love to see a write up and we could add it to the docs.

From a brief experiment my suggestion seems to be correct:

class A:
    pass


class B:
    def __eq__(self, other):
        print('used B.__eq__')
        return True


class C:
    def __eq__(self, other):
        print('used C.__eq__')
        return True


assert A() == B()  # used B.__eq__
assert B() == A()  # used B.__eq__
assert B() == C()  # used B.__eq__
assert C() == B()  # used C.__eq__

In summary: if one instance has a custom __eq__, it's used, if both (or I guess) neither do, the left hand __eq__ is used.

@Marco-Kaulea
Copy link
Contributor Author

A rich comparison method may return the singleton NotImplemented if it does not implement the operation for a given pair of arguments.

By default, object implements eq() by using is, returning NotImplemented in the case of a false comparison: True if x is y else NotImplemented.

I haven't found any documentation on how NotImplemented is handled, but it seems to cause Python to try the __eq__ method on the right hand side object.
So this library probably works, because stdlib classes return NotImplemented when they are compared to a type that they don't know.

I still haven't figured out in which situations DirtyEqualsMeta.__eq__ gets called, but I think I can accept that little piece of black magic.

I'm not sure if I can find the time to write something up, but I'll try to find the time this week for a paragraph or two.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants