Reputation: 1767
Trying to change the __unicode__
method on an instance after it's created produces different results on Python 2.5 and 2.6.
Here's a test script:
class Dummy(object):
def __unicode__(self):
return u'one'
def two(self):
return u'two'
d = Dummy()
print unicode(d)
d.__unicode__ = d.two
print unicode(d)
print d.__unicode__()
On Python 2.5, this produces
one
two
two
That is, changing the instance's __unicode__
also changes unicode(instance)
On Python 2.6, this produces
one
one
two
So, after a change, unicode(instance)
and instance.__unicode__()
return different results.
Why? How can I get this working on Python 2.6?
(For what it's worth, the use case here is that I want to append something to the output of __unicode__
for all subclasses of a given class, without having to modify the code of the subclasses.)
Edit to make the use case a little clearer
I have Class A, which has many subclasses. Those subclasses define simple __unicode__
methods. I want to add logic so that, for instances of a Class A subclass, unicode(instance) gets something tacked on to the end. To keep the code simple, and because there are many subclasses I don't want to change, I'd prefer to avoid editing subclass code.
This is actually existing code that works in Python 2.5. It's something like this:
class A(object):
def __init__(self):
self._original_unicode = self.__unicode__
self.__unicode__ = self.augmented_unicode
def augmented_unicode(self):
return self._original_unicode() + u' EXTRA'
It's this code that no longer works on 2.6. Any suggestions on how to achieve this without modifying subclass code? (If the answer involves metaclasses, note that class A is itself a subclass of another class -- django.db.models.Model -- with a pretty elaborate metaclass.)
Upvotes: 4
Views: 253
Reputation: 11522
It appears that you are not allowed to monkey-patch protocol methods (those that begin and end with double underscores) :
Note
In practise there is another exception that we haven't handled here. Although you can override methods with instance attributes (very useful for monkey patching methods for test purposes) you can't do this with the Python protocol methods. These are the 'magic methods' whose names begin and end with double underscores. When invoked by the Python interpreter they are looked up directly on the class and not on the instance (however if you look them up directly - e.g. x.repr - normal attribute lookup rules apply).
That being the case, you may be stuck unless you can go with ~unutbu's answer.
EDIT: Or, you can have the base class __unicode__
method search the instance object's dict for a __unicode__
attribute. If it's present, then __unicode__
is defined on the instance object, and the class method calls the instance method. Otherwise, we fall back to the class definition of __unicode__
.
I think that this could allow your existing subclass code to work without any changes. However, it gets ugly if the derived class wants to invoke the class implementation -- you need to be careful to avoid infinite loops. I haven't implemented such hacks in this example; merely commented about them.
import types
class Dummy(object):
def __unicode__(self):
func = self.__dict__.get("__unicode__", None)
if func:
// WARNING: if func() invokes this __unicode__ method directly,
// an infinite loop could result. You may need an ugly hack to guard
// against this. (E.g., set a flag on entry / unset the flag on exit,
// using a try/finally to protect against exceptions.)
return func()
return u'one'
def two(self):
return u'two'
d = Dummy()
print unicode(d)
funcType = type(Dummy.__unicode__)
d.__unicode__ = types.MethodType(Dummy.two, d)
print unicode(d)
print d.__unicode__()
Testing with Python 2.6 produces the following output:
> python dummy.py
one
two
two
Upvotes: 2
Reputation: 1767
Looks like Dan is correct about monkey-patching protocol methods, and that this was a change between Python 2.5 and Python 2.6.
My fix ended up being making the change on the classes rather the instances:
class A(object):
def __init__(self):
self.__class__.__unicode__ = self.__class__.augmented_unicode
Upvotes: 0
Reputation: 879919
Edit: In response to the OP's comment: Adding a layer of indirection can allow you to change the behavior of unicode
on a per-instance basis:
class Dummy(object):
def __unicode__(self):
return self._unicode()
def _unicode(self):
return u'one'
def two(self):
return u'two'
d = Dummy()
print unicode(d)
# one
d._unicode = d.two
print unicode(d)
# two
print d.__unicode__()
# two
Upvotes: 2