classification
Title: unbound methods of different classes compare equal
Type: behavior Stage: resolved
Components: Interpreter Core Versions: Python 3.0, Python 2.6
process
Status: closed Resolution: rejected
Dependencies: Superseder:
Assigned To: Nosy List: exarkun, pitrou, r.david.murray
Priority: normal Keywords:

Created on 2008-08-04 19:13 by exarkun, last changed 2010-12-22 02:39 by r.david.murray. This issue is now closed.

Messages (8)
msg70714 - (view) Author: Jean-Paul Calderone (exarkun) * (Python committer) Date: 2008-08-04 19:13
If a method is inherited by two different classes, then the unbound
method objects which can be retrieved from those classes compare equal
to each other.  For example:

  Python 2.6b2+ (trunk:65502M, Aug  4 2008, 15:05:07)
  [GCC 4.0.3 (Ubuntu 4.0.3-1ubuntu5)] on linux2
  Type "help", "copyright", "credits" or "license" for more information.
  >>> class X:
  ...     def y(self):
  ...             pass
  ...
  >>> class A(X):
  ...     pass
  ...
  >>> class B(X):
  ...     pass
  ...
  >>> A.y == B.y
  True

This is bad behavior because A.y and B.y are otherwise distinguishable
(for example, they repr differently, they have different values for the
`im_class´ attribute, they cannot be used interchangably for invoking
the method because they place different type restrictions on the `self´
parameter, etc).
msg70715 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2008-08-04 19:25
Well, I'm not sure. One could also argue that 1 and 1.0 mustn't compare
equal because they cannot be used equally in all circumstances (e.g.
__index__), they have different repr's, different types, etc.

The question is: what kind of use case does it help to have them compare
unequal? I can see the utility of having them compare equal: to check
whether a method has been overriden or not.

(I'm removing 2.4 and 2.5 anyway since the change would break compatibility)
msg70716 - (view) Author: Jean-Paul Calderone (exarkun) * (Python committer) Date: 2008-08-04 19:37
The reason I noticed this is that since they compare and hash equal, if
you put two such methods into a set, you end up with a set with one
method.  Currently, this is preventing me from running two test methods
because the method itself is defined on a base class and two subclasses
which customize several other methods inherit it.  I can only run one
test at a time.

Having them compare unequal means you can't actually trust unbound
method comparison, nor using unbound methods as keys in a dictionary. 
This means some other mapping structure is required if you want to keep
around a bunch of methods and arguments to pass to them.  It also means
that any time you want to check two methods against each other with the
goal of eventually calling one or both of them, you need to use
something other than `==´.  It seems like calling methods is likely to
be a more common use-case than anything else.
msg70717 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2008-08-04 20:07
> The reason I noticed this is that since they compare and hash equal, if
> you put two such methods into a set, you end up with a set with one
> method.  Currently, this is preventing me from running two test methods
> because the method itself is defined on a base class and two subclasses
> which customize several other methods inherit it.  I can only run one
> test at a time.

But you acknowledge they are really the same method attached to
different classes, right? The notion of "unbound method" is mostly an
implementation detail. The term occurs only 4 times in the whole Python
documentation (according to Google). And in py3k they are gone. (*)

Moreover, you say you want them to compare unequal because you
*explicitly* want the same method called separately for each class it is
defined on. Is there anything preventing you to have a set of (class,
method) tuples instead? Because it sounds like the logical thing to do
in your case.

> Having them compare unequal means you can't actually trust unbound
> method comparison, nor using unbound methods as keys in a dictionary.

"Trust" is a strong word. You can trust the comparison operator if you
agree with its semantics, you cannot trust it if you want different
semantics. But that doesn't mean it is generally trustworthy or
untrustworthy.

Really, this is the same as with numbers:

'b'

There are probably use cases where the above is annoying. But,
conversely, there are probably use cases where a stricter behaviour
would be annoying too.

> This means some other mapping structure is required if you want to keep
> around a bunch of methods and arguments to pass to them.

I disagree. The general use case of keeping a bunch of callables with
their respective arguments implies storing bound, not unbound, methods.
(how often do you feed an unbound method to an addCallback() ?)

> It also means
> that any time you want to check two methods against each other with the
> goal of eventually calling one or both of them, you need to use
> something other than `==´.

I don't think there are lots of use cases for comparing *unbound*
methods. One such use case is checking for redefinition of inherited
methods, and the current __eq__ semantics look fine for that.

(*)
Python 3.0b2+ (py3k, Jul 29 2008, 20:37:34) 
[GCC 4.3.1 20080626 (prerelease)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> class A:
...  def f(): pass
... 
>>> type(A.f)
<class 'function'>
>>> a = A()
>>> type(a.f)
<class 'method'>
>>> def g(): pass
... 
>>> class B:
...  g = g
... 
>>> B.g is g
True
msg70718 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2008-08-04 20:10
Apparently Roundup snipped my numbers example :-)
Here it is, hoping it will pass through this time :

>>> d = {}
>>> d[1] = 'a'
>>> d[1.0] = 'b'
>>> d[1]
'b'
msg70736 - (view) Author: Jean-Paul Calderone (exarkun) * (Python committer) Date: 2008-08-05 13:20
> But you acknowledge they are really the same method attached to
> different classes, right? The notion of "unbound method" is mostly an
> implementation detail. The term occurs only 4 times in the whole Python
> documentation (according to Google). And in py3k they are gone. (*)

It's the same function attached to two different classes.  I don't
really buy the "implementation detail" argument - if Guido says it,
then I don't have much choice but to accept it, but I'm going to
argue about it with anyone else. :)  In this case, I'd say it's
definitely not an implementation detail because it has major
consequences visible to applications.  If I get method x from
class A and try to call it with an instance of class B, then
it's going to break.  I have to know about this behavior in order
to write a program that works.  Py3k may be different, but I'm
not going to talk about Py3k here because I'm only interested
in the Python 2.x behavior.

> Moreover, you say you want them to compare unequal because you
> *explicitly* want the same method called separately for each class it is
> defined on. Is there anything preventing you to have a set of (class,
> method) tuples instead? Because it sounds like the logical thing to do
> in your case.

I could do that.  I probably will, too, since this code needs to work
on Python 2.3 through Python 2.5.  I still want to make Python 2.6
better, though.  It seems to me that an unbound method is already
effectively a tuple of (class, function) - it has a reference to both
of those.  Requiring applications to tie it up with a class again is
just redundant.

> "Trust" is a strong word. You can trust the comparison operator if you
> agree with its semantics, you cannot trust it if you want different
> semantics. But that doesn't mean it is generally trustworthy or
> untrustworthy.

You're right.  "trust" was a bad word to use there.

> I don't think there are lots of use cases for comparing *unbound*
> methods. One such use case is checking for redefinition of inherited
> methods, and the current __eq__ semantics look fine for that.

This use-case is already satisfied though - if an application wants
to see if the function is the same, then the application can compare
the im_func attribute of the method objects.
msg70737 - (view) Author: Antoine Pitrou (pitrou) * (Python committer) Date: 2008-08-05 14:01
Hi,

> It's the same function attached to two different classes.  I don't
> really buy the "implementation detail" argument - if Guido says it,
> then I don't have much choice but to accept it, but I'm going to
> argue about it with anyone else. :)

I understand that :-)

> Py3k may be different, but I'm
> not going to talk about Py3k here because I'm only interested
> in the Python 2.x behavior.

Granted, but Python 2.6 (and subsequent 2.x versions) strives to make it easier
to port code to 3.x by gradually reducing compatibility issues. Your proposal to
change unbound method __eq__ behaviour would, on the contrary, add another
incompatibility between 3.x and 2.x.

> This use-case is already satisfied though - if an application wants
> to see if the function is the same, then the application can compare
> the im_func attribute of the method objects.

But your use case is already satisfied as well - you just have to use the
(class, method) tuple for comparison. In other words, both use cases can be
satisfied by circumventing, if needed, the default __eq__ behaviour. If there's
no overwhelming reason to change the current default, keeping 2.x and 3.0
compatibility should prevail.

Regards

Antoine.
msg124477 - (view) Author: R. David Murray (r.david.murray) * (Python committer) Date: 2010-12-22 02:39
Since 2.7 has been released and this behaviour could not be changed in a point release even if agreement that it was a good change was reached, and since it is meaningless in 3.x, I'm closing this issue.
History
Date User Action Args
2010-12-22 02:39:01r.david.murraysetstatus: open -> closed

nosy: + r.david.murray
messages: + msg124477

resolution: rejected
stage: resolved
2008-08-05 14:01:57pitrousetmessages: + msg70737
2008-08-05 13:20:25exarkunsetmessages: + msg70736
2008-08-04 20:10:46pitrousetmessages: + msg70718
2008-08-04 20:07:50pitrousetmessages: + msg70717
2008-08-04 19:37:27exarkunsetmessages: + msg70716
2008-08-04 19:25:15pitrousetnosy: + pitrou
messages: + msg70715
versions: + Python 3.0, - Python 2.5, Python 2.4
2008-08-04 19:13:33exarkuncreate