[Patches] [ python-Patches-490402 ] Improper PyMethodObject->im_class fix

noreply@sourceforge.net noreply@sourceforge.net
Fri, 07 Dec 2001 13:49:32 -0800


Patches item #490402, was opened at 2001-12-07 13:09
You can respond by visiting: 
http://sourceforge.net/tracker/?func=detail&atid=305470&aid=490402&group_id=5470

Category: Core (C code)
Group: None
Status: Closed
Resolution: Invalid
Priority: 5
Submitted By: Robin Dunn (robind)
Assigned to: Guido van Rossum (gvanrossum)
Summary: Improper PyMethodObject->im_class fix

Initial Comment:
In wxPython's code for reflecting virtual method calls 
to Python methods I have code that looks up the method 
by name and checks the class it comes from.  If it's 
in the base Python shadow class then I just call the 
base C++ method directly to save time, otherwise I 
call the Python method.

In the Python 2.2 betas I found that the class object 
returned from PyMethod_GET_CLASS is now the class of 
the instance, not the class where the method was 
defined as it was before, and as documented in 
classobject.h.  This causes my code to recurse until 
the max stack depth is reached, and I've been unable 
to find a way to work around this change.

The differences can be further illustrated by this 
little Python program:

class A:
    def spam(self):
        print "spam"
class B(A):
    pass

import sys
print sys.version
b = B()
print b.spam
print B.spam

When run with various version of Python I get this:

[C:\temp] p15 method_test.py
1.5.2 (#0, Apr 13 1999, 10:51:12) [MSC 32 bit (Intel)]
<method A.spam of B instance at 86c958>
<unbound method A.spam>

[C:\temp] p20 method_test.py
2.0 (#8, Oct 16 2000, 17:27:58) [MSC 32 bit (Intel)]
<method A.spam of B instance at 007F4734>
<unbound method A.spam>

[C:\temp] p21 method_test.py
2.1.1 (#20, Jul 20 2001, 01:19:29) [MSC 32 bit (Intel)]
<method A.spam of B instance at 007C1A9C>
<unbound method A.spam>

[C:\temp] p22 method_test.py
2.2b2 (#26, Nov 16 2001, 11:44:11) [MSC 32 bit (Intel)]
<bound method B.spam of <__main__.B instance at 
0x00778868>>
<unbound method B.spam>


Notice the change from "A.spam" to "B.spam" in the 
final case.

Going on the assumption that this is a bug and not 
intended behaviour, the attached patch fixes this 
issue as can be seen in this run of the test, but I 
don't know if there are any other ramifications:

[C:\temp] \PROJECTS\Python-cvs\PCbuild\python_d.exe 
method_test.py
Adding parser accelerators ...
Done.
2.2b2+ (#26, Dec  7 2001, 11:46:31) [MSC 32 bit 
(Intel)]
<bound method A.spam of <__main__.B instance at 
0x007DCF80>>
<unbound method A.spam>
[3306 refs]

If this is not a bug then please let me know what the 
proper way is via the API to find the class that 
defined a particular method.



----------------------------------------------------------------------

>Comment By: Robin Dunn (robind)
Date: 2001-12-07 13:49

Message:
Logged In: YES 
user_id=53955

Thanks.

BTW, the comment on line 34 of classobject.h (about 
im_class) should probably be changed.

----------------------------------------------------------------------

Comment By: Guido van Rossum (gvanrossum)
Date: 2001-12-07 13:45

Message:
Logged In: YES 
user_id=6380

I haven't had the need to do this elsewhere, so there's no C
API.

To do it for new-style classes, look at the implementation
of _PyType_Lookup() in typeobject.c; at the point where it
returns res, the defining class is base. (Please look at the
current CVS or at least 2.2b2; there's a new special case in
the code to allow classic classes in the MRO of a new-style
class.)

For classic classes, you can copy class_lookup() from
classobject.c.

----------------------------------------------------------------------

Comment By: Robin Dunn (robind)
Date: 2001-12-07 13:38

Message:
Logged In: YES 
user_id=53955

Is there an easy way to do that in the C API?  If not, can 
you give me some hints for how to do it the hard way? <wink>

----------------------------------------------------------------------

Comment By: Guido van Rossum (gvanrossum)
Date: 2001-12-07 13:32

Message:
Logged In: YES 
user_id=6380

It's not a bug. This change has acually revealed several
bugs in existing code of the following sort:

class A:
  def method(self): ...

class B(A): ... # does not override method (yet)

class C(A):
  def method(self):
    ...do something else...
    B.method(self) # invoke base class method

Note that C doesn't inherit from B, yet the call to
B.method(self) succeeded (in prior Python versions), because
im_class was set to the defining class instead of the class
used in the getattr operation. This kind of bug occurs when
the class hierarchy is restructured but not all upcalls are
fixed, and is waiting for problems (when B grows its own
implementation of method(), C which is unrelated to B
suddenly stops working).

To find the class that defines a method, for new-style
classes you can walk the __mro__ tuple looking in the
__dict__ of each class found for the method; for classic
classes you have to walk the hierarchy using __bases__.  I
hope this is not too much of an inconvenience.

Let me know if further action is required; I'd like to close
this bug report as "Won't fix".

----------------------------------------------------------------------

Comment By: Tim Peters (tim_one)
Date: 2001-12-07 13:15

Message:
Logged In: YES 
user_id=31435

This one's for Guido.

----------------------------------------------------------------------

You can respond by visiting: 
http://sourceforge.net/tracker/?func=detail&atid=305470&aid=490402&group_id=5470