This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: special methods become static
Type: Stage:
Components: Interpreter Core Versions: Python 2.3
process
Status: closed Resolution: not a bug
Dependencies: Superseder:
Assigned To: mwh Nosy List: arigo, georg.brandl, kquick, mwh
Priority: normal Keywords:

Created on 2004-11-15 06:46 by kquick, last changed 2022-04-11 14:56 by admin. This issue is now closed.

Files
File name Uploaded Description Edit
newclass.py kquick, 2004-11-15 06:46 newclassprob.py
Messages (7)
msg23127 - (view) Author: Kevin Quick (kquick) Date: 2004-11-15 06:46
This *may* be a duplicate of 729913, but there's either additional
info here, or this is actually different.

The issue is that any special method (e.g. __call__, __str__, etc.)
defined for a new-style (i.e. object-based) class seems to be static
(i.e. unchangeable) with some special lookup applied for that method,
but this doesn't seem to be the case for regular methods.

class A:
  def foo(self): return 1
  def __call__(self): return 2
  def bar(self): return 3
  def adjust(self):
    self.foo = self.bar
    self.__call__ = self.bar

a = A()
print a.foo(), a()
a.adjust()
print a.foo(), a()

Will print:
1 2
3 3

But if the A is turned into a new-style class by changing the
first line:

class A(object):

then the printed results are:
1 2
3 2

To the best of my understanding of the migration from classic classes 
to new-style classes (and metaclassing), this shouldn't occur.  I have 
also tried various name munging for the special method (e.g. setting 
_B__call__, using setattr, etc.), but I haven't found the special trick 
yet.

The attached script shows the example in more detail.
msg23128 - (view) Author: Michael Hudson (mwh) (Python committer) Date: 2004-11-15 07:16
Logged In: YES 
user_id=6656

The change you are observing is that special methods are now 
only looked up on the *class* of the object concerned.  This, or 
something like this is actually unavoidable -- in

>>> repr(o)

what do you do if both o and the type of o define a __repr__ 
method?

There are articles on new-style classes out there that should 
explain this in more depth.  It probably could/should be 
documented better in the core -- there are bugs open to that 
effect already.

Closing.
msg23129 - (view) Author: Kevin Quick (kquick) Date: 2004-11-15 14:29
Logged In: YES 
user_id=6133

OK, I didn't find anything documenting this change anywhere (obviously).

I read Guido's description of new-classes (www.python.org/2.2/descrintro.py)
and the various documentation, but either I overlooked it therein or it's talked 
about somewhere else.

I'm curious as to why special methods are treated specially in regards to this 
lookup.  Specifically for your example, why wouldn't you just use the 
__repr__ attribute of o if it exists, instead of o's class, just like it does for 
non-special methods?  Can you briefly explain this or provide me with a
reference?

Leaving this closed is OK with me since it's apparently known and expected... 
I'd just like to understand it a bit better.  Sorry for the bandwidth, and Thanks!
msg23130 - (view) Author: Michael Hudson (mwh) (Python committer) Date: 2004-11-16 08:22
Logged In: YES 
user_id=6656

Oh, sorry, I think I got that a bit wrong.  The issue is more with 
bound/unbound methods -- given 

class O(object):
 def __repr__(self):
   ...

should O.__repr__ be a bound method (O is an instance of type) 
or an unbound method so O.__repr__(O()) does what you expect?

Python chooses the latter, but this means that you can't implement 
the builtin function repr as 

def repr(o):
    return o.__repr__()

Hope this helps, a little at least.
msg23131 - (view) Author: Kevin Quick (kquick) Date: 2004-12-22 19:00
Logged In: YES 
user_id=6133

Thanks for the clarifcation.  However IMHO it is wrong to have different 
behavior for different methods.

To wit, if I defined a method, it is a bound method, and ergo a "self" initial 
argument is automatically supplied.  However, a __repr__, even though I 
define it, acts as an unbound method, with the self argument having a default 
of the current instance but overrideable if an argument is supplied on the call.
This means that the argument evaluation/passing is different for these types
of builtins as opposed to other methods, both of which I have defined myself.

This just doesn't seem right to me, which is why I'm re-opening this bug 
report... sorry to be annoying, but this definitely seems inconsistent and a 
better explanation.

My current workaround is to do the following:

class foo:
  def __str__(self):
    return my_str()
  def my_str(self):
    return 'something'

So that I can do "foo.my_str = lambda self: return 'something else'".

When what I should be able to do is:

class foo:
  def __str__(self):
    return 'something'

...
foo.__str__ = lambda self: return 'something else'
msg23132 - (view) Author: Georg Brandl (georg.brandl) * (Python committer) Date: 2005-06-26 20:55
Logged In: YES 
user_id=1188172

It is not the case that the methods are unchangeable. See
this example:

class A(object):
    def __repr__(self): return 'repr'
    def new_repr(self): return 'new_repr'
a = A()
a.__repr__ = a.new_repr
print a.__repr__()
=> prints "new_repr"
print repr(a)
=> prints "repr"

msg23133 - (view) Author: Armin Rigo (arigo) * (Python committer) Date: 2005-12-26 17:21
Logged In: YES 
user_id=4771

Re-closing.  This is a known documentation bug: all this is
expected, but just under-documented.  'str(x)' issues a
special method call to '__str__' on 'x', but the real
definition of "calling a special method" on an object 'x' is
as follows: look up the name "__str__" in the dict of
type(x), then in the dict of the parent types in MRO order.
 It's really not the same thing as an attribute lookup.

The reason for this, to put Michael's argument differently,
is that if 'str(x)' would really work like 'x.__str__()',
then this is what would occur:

>>> class X(object):
...    def __repr__(self):
...        return "hello"
...
>>> X()
hello
>>> X
TypeError: __repr__() takes exactly 1 argument (0 given)

because X.__repr__() is just an unbound method call with a
missing argument.  There is no such thing as "default values
for self" in Python.
History
Date User Action Args
2022-04-11 14:56:08adminsetgithub: 41172
2004-11-15 06:46:24kquickcreate