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

Add equality rich comparision operators to telegram objects #604

Merged
merged 19 commits into from May 14, 2017

Conversation

Projects
None yet
4 participants
@jsmnbom
Member

jsmnbom commented May 12, 2017

Fixes #587 by adding __eq__ and __hash__ methods to the base TelegramObject class to make them work with the == operator.
This makes the following classes behave correctly:

  • Animation
  • Audio
  • Bot
  • CallbackQuery
  • Chat
  • ChosenInlineResult
  • Contact
  • Document
  • File
  • Game
  • InlineQuery
  • InlineQueryResult*
  • Message
  • PhotoSize (already had a __eq__ implementation, is it still needed?)
  • Sticker
  • Update
  • Video
  • Voice

There's still a few classes that still don't work since they don't have an id. Many of them I don't think needs handling of this (like InlineKeyboard, I don't think anyone ever needs to compare that), but the following we should maybe try to find a solution to. For now I just wanted to get the basics working, to get your opinion on weather this is the way we wanna do it.

  • ChatMember
  • Game
  • GameHighScore
  • Location
  • MessageEntity
  • Venue
  • WebhookInfo?

We might also wanna have a look at the ext classes to see if any of them need __eq__ too.
Still lacking tests. Should I test all of them in their respective test_CLASSNAME or should I add a new test_equality.py test?

Show outdated Hide outdated telegram/base.py
@jsmnbom

This comment has been minimized.

Show comment
Hide comment
@jsmnbom

jsmnbom May 12, 2017

Member

@tsnoam could you shrine some light on why PhotoSize has a __eq__ method? It was implemented in ec8cd37 for some reason...
I know it was two years ago, so you probably don't :P

Member

jsmnbom commented May 12, 2017

@tsnoam could you shrine some light on why PhotoSize has a __eq__ method? It was implemented in ec8cd37 for some reason...
I know it was two years ago, so you probably don't :P

@Eldinnie

This comment has been minimized.

Show comment
Hide comment
@Eldinnie

Eldinnie May 12, 2017

Member

Seems pretty solid. I added a comment.
I think the photosize could as well be deleted, since it checks size too, but I think if file_id is different the rest is different as well.
for the excluded cases I think maybe implement them in their seperate classes.

  • ChatMember (user == user and status == status)
  • Game (title?)
  • GameHighscore, seems pointless, but for completion?
  • Location (lat == lat and lon == lon)
  • MessageEntity Seems pointless
  • Venue (lat==lat and lon==lon and title==title)
  • WebhookInfo no...

Regarding tests, I would vote for adding them in their respective classes.

Member

Eldinnie commented May 12, 2017

Seems pretty solid. I added a comment.
I think the photosize could as well be deleted, since it checks size too, but I think if file_id is different the rest is different as well.
for the excluded cases I think maybe implement them in their seperate classes.

  • ChatMember (user == user and status == status)
  • Game (title?)
  • GameHighscore, seems pointless, but for completion?
  • Location (lat == lat and lon == lon)
  • MessageEntity Seems pointless
  • Venue (lat==lat and lon==lon and title==title)
  • WebhookInfo no...

Regarding tests, I would vote for adding them in their respective classes.

@Eldinnie

This comment has been minimized.

Show comment
Hide comment
@Eldinnie
Member

Eldinnie commented May 12, 2017

@jsmnbom

This comment has been minimized.

Show comment
Hide comment
@jsmnbom

jsmnbom May 12, 2017

Member

For the tests :D see test_video.py line 159 and test_video.py line 159

Ahh, thanks, I see :D

@Eldinnie I agree with implementing the other in their respective classes ^^

Member

jsmnbom commented May 12, 2017

For the tests :D see test_video.py line 159 and test_video.py line 159

Ahh, thanks, I see :D

@Eldinnie I agree with implementing the other in their respective classes ^^

@tsnoam

This comment has been minimized.

Show comment
Hide comment
@tsnoam

tsnoam May 12, 2017

Member

@bomjacob As far as i recall, telegram servers started to return actual data in reply to sending certain messages (sendPhoto from a url?)
So i had to actually compare the received data with what we expected it to be.

I believe that comparing id may be enough but I don't have insight into telegram internals...

Member

tsnoam commented May 12, 2017

@bomjacob As far as i recall, telegram servers started to return actual data in reply to sending certain messages (sendPhoto from a url?)
So i had to actually compare the received data with what we expected it to be.

I believe that comparing id may be enough but I don't have insight into telegram internals...

@Y0hy0h

This comment has been minimized.

Show comment
Hide comment
@Y0hy0h

Y0hy0h May 13, 2017

I want to point out an alternative to having the equality check for identity:

One could also have two objects, User1<id=1, first_name="Same"> and User2<id=2, first_name="Same">, which are equal in the sense that they have exactly the same "relevant" attributes, but are not identical since they are two different objects (hence having different ids).
I would argue that it still would make sense if User1 == User2 would return true, since they are the same concerning all relevant attributes.
If I wanted to check for identity, I could use User1 is User2, which returns true if the variables reference the exact same instance. Since the id should be unique, there should never be two instances with the same id and thus this check would be reliable.

I understand however that the use case for the equality operator might mostly be to check for identity, in which case the implementation based on id is perfect. I still wanted to point this out to be able to discuss it.

Y0hy0h commented May 13, 2017

I want to point out an alternative to having the equality check for identity:

One could also have two objects, User1<id=1, first_name="Same"> and User2<id=2, first_name="Same">, which are equal in the sense that they have exactly the same "relevant" attributes, but are not identical since they are two different objects (hence having different ids).
I would argue that it still would make sense if User1 == User2 would return true, since they are the same concerning all relevant attributes.
If I wanted to check for identity, I could use User1 is User2, which returns true if the variables reference the exact same instance. Since the id should be unique, there should never be two instances with the same id and thus this check would be reliable.

I understand however that the use case for the equality operator might mostly be to check for identity, in which case the implementation based on id is perfect. I still wanted to point this out to be able to discuss it.

@jsmnbom

This comment has been minimized.

Show comment
Hide comment
@jsmnbom

jsmnbom May 13, 2017

Member

That's not quite how python works though....

In [4]: user1 = telegram.User(1, "Same")

In [5]: user2 = telegram.User(2, "Same")

In [6]: user1 is user2
Out[6]: False
Member

jsmnbom commented May 13, 2017

That's not quite how python works though....

In [4]: user1 = telegram.User(1, "Same")

In [5]: user2 = telegram.User(2, "Same")

In [6]: user1 is user2
Out[6]: False

@jsmnbom jsmnbom changed the title from Add equality rich comparision operator to base class to Add equality rich comparision operators to telegram objects May 13, 2017

@Y0hy0h

This comment has been minimized.

Show comment
Hide comment
@Y0hy0h

Y0hy0h May 13, 2017

But that's exactly what I'd want:

user1 = telegram.User(1, "Same, but not identical")
user2 = telegram.User(2, "Same, but not identical")
user3 = user2  # Identical

I'd expect:

user1 == user2  # True
user1 is user2  # False
user2 == user3  # True
user2 is user3  # True

The critical thing is that there shouldn't be the possibility of creating two objects user1 = telegram.User(1, "Identical") and user2 = telegram.User(1, "Identical") with the same id for this to work perfectly. However, I'm sure that it is in principal possible to create those two objects, since Python would not enforce the id being unique.

Also, just to be explicit: I don't want to insist on this implementation. I just wanted to pitch this alternative and discuss it. It might be more powerful but I also suspect that this is not intuitive and too specialized.

Also thanks for taking your time to implement it! :)

Y0hy0h commented May 13, 2017

But that's exactly what I'd want:

user1 = telegram.User(1, "Same, but not identical")
user2 = telegram.User(2, "Same, but not identical")
user3 = user2  # Identical

I'd expect:

user1 == user2  # True
user1 is user2  # False
user2 == user3  # True
user2 is user3  # True

The critical thing is that there shouldn't be the possibility of creating two objects user1 = telegram.User(1, "Identical") and user2 = telegram.User(1, "Identical") with the same id for this to work perfectly. However, I'm sure that it is in principal possible to create those two objects, since Python would not enforce the id being unique.

Also, just to be explicit: I don't want to insist on this implementation. I just wanted to pitch this alternative and discuss it. It might be more powerful but I also suspect that this is not intuitive and too specialized.

Also thanks for taking your time to implement it! :)

@jsmnbom

This comment has been minimized.

Show comment
Hide comment
@jsmnbom

jsmnbom May 13, 2017

Member

Oh that was what you meant?
Well.. that's pretty much the way to handle these things... you should really never mess with the id implementation :D

Member

jsmnbom commented May 13, 2017

Oh that was what you meant?
Well.. that's pretty much the way to handle these things... you should really never mess with the id implementation :D

@Eldinnie

This comment has been minimized.

Show comment
Hide comment
@Eldinnie

Eldinnie May 13, 2017

Member

@Y0hy0h If you want to compare if users have the same first name, you could do that manually. What we want to achieve is to figure out if two User instances represent the same user. Checking if they have a similar feature is not what we're trying to achieve.

What you suggest is like saying you live at privet drive nr 4. and when I come to visit I get to diagon ally nr 4 and expect you to be there.

Member

Eldinnie commented May 13, 2017

@Y0hy0h If you want to compare if users have the same first name, you could do that manually. What we want to achieve is to figure out if two User instances represent the same user. Checking if they have a similar feature is not what we're trying to achieve.

What you suggest is like saying you live at privet drive nr 4. and when I come to visit I get to diagon ally nr 4 and expect you to be there.

@tsnoam

This comment has been minimized.

Show comment
Hide comment
@tsnoam

tsnoam May 13, 2017

Member

The is operator is meant to be used to compare that two objects are exactly the same object, in memory. Changing our implementation to always return (for example) the same User instance for the same user will create undesirable side effects, like, the consumer of User won't be able to change any value inside (not sure why one would want it, but still) without changing it for all other places his program, whether he'd like it or not. (it will be kind of a singleton per user).
At the devel team, we find this behaviour non intuitive and "non pythonic".
Therefor, the implementation would be for a == (equality) operator.

Member

tsnoam commented May 13, 2017

The is operator is meant to be used to compare that two objects are exactly the same object, in memory. Changing our implementation to always return (for example) the same User instance for the same user will create undesirable side effects, like, the consumer of User won't be able to change any value inside (not sure why one would want it, but still) without changing it for all other places his program, whether he'd like it or not. (it will be kind of a singleton per user).
At the devel team, we find this behaviour non intuitive and "non pythonic".
Therefor, the implementation would be for a == (equality) operator.

jsmnbom added some commits May 13, 2017

Equality tests part 1
Audio, Document, File, Location, Photo, Sticker, Venue, Video and Voice
@Y0hy0h

This comment has been minimized.

Show comment
Hide comment
@Y0hy0h

Y0hy0h May 13, 2017

@Eldinnie It's not about checking the name. It's more like a = 5 and b = 5 being two different objects (at least for the sake of the argument) but evaluating as a == b # True. The user's id is similar to the memory address of the object: It's a representation for an instance. It would not count into the natural notion of two users being equal (having exactly the same attributes). However, I get what you're getting at: In code, most of the time we want to check if the two users are identical, not if they are the same regarding all relevant (i. e. ignoring id) attributes.

@tsnoam That's a good point against enforcing users to be singletons. That also makes checking by is invalid, leaving only the identity check through ==. 👍

My suggestion was just something that was intuitive to me. I simply feel that == works more like Java's .equals function for strings, in that it should indicate whether objects are the same, not if they are the same instance. This intuition gets really tricky with an id attribute, since from a enduser's perspective, the id is invisible and wouldn't count into their notion of two users being the same.
I totally agree that it is justified to use == for checking if two users are identical from the API's point of view! Let's just keep the implementation above, then. :)

Also thank you for your patience and answers! It's really awesome that you addressed my issue in such detail!

Y0hy0h commented May 13, 2017

@Eldinnie It's not about checking the name. It's more like a = 5 and b = 5 being two different objects (at least for the sake of the argument) but evaluating as a == b # True. The user's id is similar to the memory address of the object: It's a representation for an instance. It would not count into the natural notion of two users being equal (having exactly the same attributes). However, I get what you're getting at: In code, most of the time we want to check if the two users are identical, not if they are the same regarding all relevant (i. e. ignoring id) attributes.

@tsnoam That's a good point against enforcing users to be singletons. That also makes checking by is invalid, leaving only the identity check through ==. 👍

My suggestion was just something that was intuitive to me. I simply feel that == works more like Java's .equals function for strings, in that it should indicate whether objects are the same, not if they are the same instance. This intuition gets really tricky with an id attribute, since from a enduser's perspective, the id is invisible and wouldn't count into their notion of two users being the same.
I totally agree that it is justified to use == for checking if two users are identical from the API's point of view! Let's just keep the implementation above, then. :)

Also thank you for your patience and answers! It's really awesome that you addressed my issue in such detail!

@Eldinnie

This comment has been minimized.

Show comment
Hide comment
@Eldinnie

Eldinnie May 13, 2017

Member

@Y0hy0h
The problem is that in your example (a=5 b=5) a and b are not the objects, 5 is. a and b are both pointing to that same object.
I still don't understand why you would want two users with different ID evaluate as equal, but maybe it's better to keep this discussion away from this thread.

Member

Eldinnie commented May 13, 2017

@Y0hy0h
The problem is that in your example (a=5 b=5) a and b are not the objects, 5 is. a and b are both pointing to that same object.
I still don't understand why you would want two users with different ID evaluate as equal, but maybe it's better to keep this discussion away from this thread.

@tsnoam

See comments on the changes.
Basically, the comments about how to hash/eq/raise are relevant for all types.

Show outdated Hide outdated telegram/base.py
Show outdated Hide outdated telegram/base.py
Show outdated Hide outdated telegram/base.py
Show outdated Hide outdated telegram/base.py
Show outdated Hide outdated telegram/base.py
Show outdated Hide outdated telegram/base.py
Show outdated Hide outdated telegram/base.py
Show outdated Hide outdated telegram/base.py
Show outdated Hide outdated telegram/chatmember.py
@tsnoam

tsnoam approved these changes May 14, 2017

@tsnoam

This comment has been minimized.

Show comment
Hide comment
@tsnoam

tsnoam May 14, 2017

Member

Great work @bomjacob !

Member

tsnoam commented May 14, 2017

Great work @bomjacob !

@tsnoam tsnoam merged commit da01601 into master May 14, 2017

1 check was pending

continuous-integration/travis-ci/pr The Travis CI build is in progress
Details

@tsnoam tsnoam deleted the fix-equals branch May 14, 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment