"Aliasing" an object's __str__ to a different method
Bengt Richter
bokr at oz.net
Sun Jul 24 01:00:35 EDT 2005
On Sat, 23 Jul 2005 10:59:56 -0400, "Jeffrey E. Forcier" <jforcier at strozllc.com> wrote:
>Thanks for all the additional replies thus far!
>
>Apparently the issue, as stated implicitly or explicitly by most of
>you, is that new-style class instances essentially defer their magic
>methods to the class's static versions of same. This is good to know :)
Actually, it's not just the "magic" methods. If you have an instance
a of a newstyle class A, any attribute lookup a.attr will undergo the
same search first to see if attr is a descriptor object, and if not,
*then* to look in the instance attribute directory. But the descriptor
search doesn't start in inst.__dict__, it goes through the chain of
classes and base classes provided by type(inst).mro(), which starts in
type(inst). And for our class A instance a, type(a) will be A, so the
search for a.attr starts there. Same applies to a.__str__. This ensures
that all instances of the same class will share the same methods. The way
a method, which is just a class variable with a function as its value, gets
to be a callable bound method, is the same as any attribute lookup looking
for a descriptor with a __get__ method (which a function also has, for this purpose).
If the descriptor doesn't have a __set__ method as well, then an instance attribute
takes priority. If there is a __set__ method, and instance attribute can't shadow
the attribute name, and the descriptor __get__ method takes precedence. Unshadowed,
a method search looks something like
cbm = ((base for base in type(inst).mro() if 'attr' in base.__dict__)
.next().__dict__['attr'].__get__(inst, type(inst)))
if this doesn't succeed and meet the __set__ vs shadowing logic, then you get
the instance attribute per se.
Jumping ahead using a MyClass inst as an example:
>>> cbm = ((base for base in type(inst).mro() if '__str__' in base.__dict__)
... .next().__dict__['__str__'].__get__(inst, type(inst)))
>>> cbm
<bound method MyClass.View of I, <__main__.MyClass object at 0x02FB91AC>, am being viewed>
This looks a little strange because the repr of the bound method includes a repr of the thing bound to,
which returns the Edit/View presentation (after the 'of' above).
(A possible reason to consider using just __str__ and letting __repr__ be).
A function's __get__ method will deliver a bound method if the lookup
is via an inst, or an unbound method if the lookup is via the class, in which
case None is passed to __get__ instead of the instance.
The upshot is that you can create descriptors __str__ and __repr__for your class that will
return bound methods using __str__ and __repr__ function attributes of your instance (if they exist)
instead of the normal class attributes, and we can chose from several normal class attributes according
to a general mode flag of the class. E.g., using your original example, and rearranging things a bit,
we can do something like what (I think) you wanted: (The first class below defines descriptors and the
example uses two instances in MyClass)
>>> class InstanceMethodSetterAndBinder(object):
... def __init__(self, inst_attr_name):
... # lambda self:'?? no %s format ??'% object.__repr__(self)
... self.inst_attr_name = inst_attr_name
... def __get__(self, inst, cls=None):
... if inst is None: return self
... default = getattr((cls or type(inst)), 'SharedEVMethod', None)
... if default is None:
... def default(self):
... return '<?? no SharedEVMethod for %s ??>'% object.__repr__(inst)
... return vars(inst).get(self.inst_attr_name, default).__get__(inst, cls)
... def __set__(self, inst, value):
... inst.__dict__[self.inst_attr_name] = value
... def __delete__(self, inst):
... try: del inst.__dict__[self.inst_attr_name]
... except KeyError: pass
...
>>> class MyClass(object):
... __str__ = InstanceMethodSetterAndBinder('__str__')
... __repr__ = InstanceMethodSetterAndBinder('__repr__')
... @staticmethod
... def Edit(self):
... return "I, %s, am being edited" % (object.__repr__(self)) # %s on self recurses!!
... @staticmethod
... def View(self):
... return "I, %s, am being viewed" % (object.__repr__(self)) # %s on self recurses!!
... SharedEVMethod = View
... def setEdit(self, shared=True):
... if not shared:
... self.__str__ = self.__repr__ = self.Edit
... else:
... type(self).SharedEVMethod = self.Edit
... def setView(self, shared=True):
... if not shared:
... self.__str__ = self.__repr__ = self.View
... else:
... type(self).SharedEVMethod = self.View
... def delInstEVM(self):
... del self.__str__
... del self.__repr__
...
>>> inst = MyClass()
>>> inst2 = MyClass()
>>> inst3 = MyClass()
>>> inst
I, <__main__.MyClass object at 0x02FB91AC>, am being viewed
>>> inst2
I, <__main__.MyClass object at 0x02FB91CC>, am being viewed
>>> inst3
I, <__main__.MyClass object at 0x02FB91EC>, am being viewed
>>> inst.setEdit()
>>> inst
I, <__main__.MyClass object at 0x02FB91AC>, am being edited
>>> inst2
I, <__main__.MyClass object at 0x02FB91CC>, am being edited
>>> inst3
I, <__main__.MyClass object at 0x02FB91EC>, am being edited
>>> inst2.setView(False)
>>> inst
I, <__main__.MyClass object at 0x02FB91AC>, am being edited
>>> inst2
I, <__main__.MyClass object at 0x02FB91CC>, am being viewed
>>> inst3
I, <__main__.MyClass object at 0x02FB91EC>, am being edited
>>> inst2.setView() # all
>>> inst2.setEdit(False) # just inst
>>> inst
I, <__main__.MyClass object at 0x02FB91AC>, am being viewed
>>> inst2
I, <__main__.MyClass object at 0x02FB91CC>, am being edited
>>> inst3
I, <__main__.MyClass object at 0x02FB91EC>, am being viewed
>>> inst2.delInstEVM()
>>> inst2
I, <__main__.MyClass object at 0x02FB91CC>, am being viewed
You could use this kind of thing in a base class and specialize
in subclasses to override stuff, and you can do other stuff too.
Your choices are more varied that you probably thought ;-)
>
>Ironically, the reason I'm using new-style classes is that I only
>read up on them very recently, and was attempting to get myself into
>the habit of inheriting from 'object' by default. Go figure.
>
>Anyway, to take a step back, the *actual* actual reason for my
>investigation into this issue is that I'm attempting to create a
>collection of classes representing HTML page/form elements, many of
>which are represented differently depending on whether they're in a
>"view" or "edit" state.
>
>And ideally I wanted to be able to hold a collection of these objects
>and toggle them all to one state or the other, then bandy them about
^^^^^^^^^^^^^^^
does that mean all instances with a single toggling action, or each
individually? You could have both. I.e., a general mode flag and the
ability to assign an arbitrary __str__ and/or __repr__ override for
a particular instance. See example, where all are toggled by default.
>as if they were strings, e.g. mixing them with literal strings via str
>(obj).
Note that your example set __repr__ also, which is not used for str(x)
if x.__str__ exists.
>
>Clearly there are at least a handful of ways to accomplish this, but
>the one that came to mind first was, as I said at the beginning, to
>define both behaviors on each object and then have the ability to
>point __str__ to one or the other. I suppose now I need to figure out
>which is more important, that ease-of-use of overriding __str__ or
>whatever benefits new-style classes give (something I'm still a bit
>unclear on).
>
Those aren't your only choices ;-)
Regards,
Bengt Richter
More information about the Python-list
mailing list