Re: "Aliasing" an object's __str__ to a different method
Available news archives: comp.lang.tcl - comp.lang.python - comp.security.firewalls - sci.crypt - comp.lang.php - comp.lang.javascript
Google
 
Web news.hping.org


comp.lang.python archive

Re: "Aliasing" an object's __str__ to a different method

From: Bengt Richter <bokr@oz.net>
Date: Sun Jul 24 2005 - 07:00:35 CEST

On Sat, 23 Jul 2005 10:59:56 -0400, "Jeffrey E. Forcier" <jforcier@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
Received on Thu Sep 29 17:08:01 2005