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

Fix comparison methods for python3 #1538

Closed

Conversation

nils-van-zuijlen
Copy link
Member

Rebased to merge in 0.10, original PR : #1537

Copy link
Member

@peternewman peternewman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some specific comments.

Also generally, I wonder if we're better off implementing cmp and then reusing that elsewhere, so the actual comparison is only detailed once, rather than having the things being compared in lots of places.

Although apparently cmp is deprecated...

@peternewman
Copy link
Member

You'll need to cherry-pick this commit to fix some of the Codespell issues:
34c9433

And 458fbb9

And others which are probably in #1532, but likely jumbled with other bits unfortunately.

@nils-van-zuijlen
Copy link
Member Author

But cherry picking commits from master will make horrible merge conflicts when you will merge master in 0.10 (or the opposite).

Maybe I should go back to merging it into master, no ?

@peternewman
Copy link
Member

peternewman commented Feb 17, 2019

But cherry picking commits from master will make horrible merge conflicts when you will merge master in 0.10 (or the opposite).

Maybe I should go back to merging it into master, no ?

My git-fu is probably rubbish, but I normally seem to get away with it, I've done it a few times. It just depends where people are working when some of those CI type things pop up where they get fixed first.

I'd like to get the bugfix into 0.10 if possible.

functools.total_ordering is only available from python 3.2
fix flake8 formatting
nils-van-zuijlen and others added 4 commits February 19, 2019 18:47
When you call RegisterUniverse with action set to UNREGISTER, you don't want to be forced to provide a data_callback
@nils-van-zuijlen
Copy link
Member Author

Normally I have addressed all the changes you requested. You may merge now that Travis is on a happy mood 😄

Copy link
Member

@peternewman peternewman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few more comments sorry.

As stated in the doc (https://docs.python.org/2.7/reference/datamodel.html?highlight=cmp#object.__cmp__) __cmp__ is only called if rich comparison is not defined and we define it as it is the only way to support python 3
@nils-van-zuijlen
Copy link
Member Author

It works !
Does it do what you wanted ?

Copy link
Member

@peternewman peternewman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few minor comments.

Other than that, could we add some tests for these new comparisons please. You can probably use existing objects used elsewhere for the tests.

@@ -51,18 +51,37 @@ def __hash__(self):
def __repr__(self):
return self.__str__()

def __cmp__(self, other):
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this class should implement __hash__ because anyone could write code like this which would change the value of the class :

address = MACAddress(bytearray([0x01, 0x23, 0x45, 0x67, 0x89, 0xab]))  # creates a MACAddress
mac = address.mac_address  # gets its representation inside the class
print(hash(address))  # print the class' hash value, for example 1043151800894266584
mac[0] = 0xff  # changes the MAC address, shouldn't be possible but it is.
print(hash(address))  # prints the class' hash value, different from the first one, for example 6608185319733256019

And would get different results at the two print statements, without violating any python (implicit) rules.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, this agrees it's bad:
https://stackoverflow.com/questions/13041352/python-quickly-hash-mutable-object

Going id based would solve our issue wouldn't it? Even if someone fiddles with the value, it's still the same object? Probably not ideal, but at least safer.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should still try and fix this please. It looks like a Python module just hashes the value:
https://github.com/drkjam/netaddr/blob/rel-0.7.x/netaddr/eui/__init__.py#L550-L552

But I'd agree we should do something or switch to "id".

@peternewman peternewman added this to the 0.10.8 milestone Mar 7, 2019
Copy link
Member

@peternewman peternewman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few small comments please @nils-van-zuijlen . Also could we have some tests of all this shiny new functionality please.

nils-van-zuijlen and others added 3 commits March 23, 2019 18:13
And add a comment for the future, when 2.7 will not be supported anymore and we will be free to use functools.total_ordering as we want, without hitting recursion limits.
Copy link
Member

@peternewman peternewman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some minor bits, sorry I dropped the ball on this completely @nils-van-zuijlen

Fancy adding some tests too?

return 1
return cmp(self.mac_address, other.mac_address)
def __eq__(self, other):
if other.__class__ is not self.__class__:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to do:
if other is None or other.__class__ is not self.__class__:

To protect against other being None, or does None still have class?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the topic of None.__class__, anecdotally it has __class__:

$ python2
Python 2.7.15+ (default, Nov 27 2018, 23:36:35) 
[GCC 7.3.0] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> None.__class__
<type 'NoneType'>
$ python3
Python 3.6.8 (default, Jan 14 2019, 11:02:34) 
[GCC 8.0.1 20180414 (experimental) [trunk revision 259383]] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> None.__class__
<class 'NoneType'>

isinstance(other, self.__class__) would take care of this for you.

If isinstance isn't desired, it seems like a more bulletproof approach might be to wrap these comparisons in a try except AttributeError block, because it is ambiguous in the documentation of __class__ which instances have __class__. In Python 3 a value not having a __class__ attribute is an even more distant edge case.

Is there some reason this doesn't this return NotImplemented like the other operators?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't return NotImplemented because if classes don't match, the two objects aren't equals.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you want to switch to isinstance @nils-van-zuijlen or is there some reason not to do so?

return self.mac_address < other.mac_address

def __eq__(self, other):
# These 4 can not be replaced with functools.total_ordering because of the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this be the same that they could "be replaced with functools.total_ordering when support for 2.7 is dropped because NotImplemented is not supported by it before 3.4"? Or even when we're on Python 3.4 will functools.total_ordering still not be sufficient?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, we need to implement these functions ourselves because we have a special treatment applied to None that functools.total_ordering doesn't support.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this because otherwise sorting None and a MAC you'd get stuck or something? Can we have a bit of a comment explaining why in the code please @nils-van-zuijlen ?

@peternewman
Copy link
Member

Sorry @nils-van-zuijlen I've been rubbish again. Any chance of some tests? Apart from that it essentially looks good to go thanks!

@brucelowekamp
Copy link
Contributor

I have tests for most of these written and should be able to submit a PR in a few days, though I'm not sure if I can add to this or need to open a new one.

@peternewman is there a need to support python older than 2.7? Quite a bit is simpler if we can assume 2.7 and recent 3.

@nils-van-zuijlen 2.7 functools appears to have backports to support everything needed here

A couple more fixes and bugs found with the tests...

@brucelowekamp brucelowekamp mentioned this pull request Jan 3, 2020
@peternewman
Copy link
Member

@nils-van-zuijlen or @brucelowekamp I think this can be closed now looking at the comparison:
0.10...nils-van-zuijlen:0.10-python3

Would you agree this has all gone in via #1605?

@nils-van-zuijlen
Copy link
Member Author

Yes it can be closed, all meaningful commits are in #1605

@peternewman
Copy link
Member

Perfect and thanks again for your efforts @nils-van-zuijlen !

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 this pull request may close these issues.

4 participants