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

Discussion in 'Python' started by Jeffrey E. Forcier, Jul 23, 2005.

  1. 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 :)

    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
    as if they were strings, e.g. mixing them with literal strings via str
    (obj).

    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).

    Thanks again,
    Jeff

    --
    Jeffrey E. Forcier
    Junior Developer, Research and Development
    Stroz Friedberg, LLC
    15 Maiden Lane, 12th Floor
    New York, NY 10038
    [main]212-981-6540 [direct]212-981-6546
    http://www.strozllc.com

    This message is for the named person's use only. It may contain
    confidential, proprietary or legally privileged information. No right to
    confidential or privileged treatment of this message is waived or lost
    by any error in transmission. If you have received this message in
    error, please immediately notify the sender by e-mail or by telephone at
    212.981.6540, delete the message and all copies from your system and
    destroy any hard copies. You must not, directly or indirectly, use,
    disclose, distribute, print or copy any part of this message if you are
    not the intended recipient.
     
    Jeffrey E. Forcier, Jul 23, 2005
    #1
    1. Advertising

  2. On Sat, 23 Jul 2005 10:59:56 -0400, "Jeffrey E. Forcier" <> 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
     
    Bengt Richter, Jul 24, 2005
    #2
    1. Advertising

  3. On Jul 24, 2005, at 5:00 AM, Bengt Richter wrote:

    > Actually, it's not just the "magic" methods. [...]


    Thanks for the clarification/explanation =)

    > 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).


    Yea, I must apologize, I pasted in a slightly different version of
    the class in my original email than I intended to...I fully
    understand the difference between __str__ and __repr__ and that bit
    of code where I attempted to bind to both was me attempting to
    discern exactly what was going on. The "real" version would only be
    binding to __str__ and leaving __repr__ alone so it could be used as
    intended =)

    > 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.


    [big-arse snip of code/output]

    I half-understood this and what followed, but that's my fault and not
    that of your explanation. Honestly, I'm still trying to elevate my
    Python knowledge from a primarily quick-scripting level to one
    suitable for larger applications (or at least, more intelligent
    scripts =)). Specifically with things like descriptors and their ilk,
    I need to reread the various docs and PEPs out there.

    But, thanks for the detailed example, and you can be sure I'll return
    to it in the near future after some more reading.


    > 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 ;-)


    Oh, I know there's a whole host of ways to accomplish this, I just
    tend to get narrowly focused on a single option or couple of options
    for stretches of time, heh.

    >> 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.


    Well, either, depending on how flexible I want the overall design to
    be, but primarily all at once. E.g. I'd have a Page or PageBody
    container class holding a bunch of (implementing the same interface)
    objects as discussed, and would want all of those sub-objects to be
    in the same mode. In other words...a page is either displaying text
    fields for editing, or plain text for display (but with myriad form
    elements, not just text fields, of course).


    Thanks again,
    Jeff

    --
    Jeffrey E. Forcier
    Junior Developer, Research and Development
    Stroz Friedberg, LLC
    15 Maiden Lane, 12th Floor
    New York, NY 10038
    [main]212-981-6540 [direct]212-981-6546
    http://www.strozllc.com

    This message is for the named person's use only. It may contain
    confidential, proprietary or legally privileged information. No right to
    confidential or privileged treatment of this message is waived or lost
    by any error in transmission. If you have received this message in
    error, please immediately notify the sender by e-mail or by telephone at
    212.981.6540, delete the message and all copies from your system and
    destroy any hard copies. You must not, directly or indirectly, use,
    disclose, distribute, print or copy any part of this message if you are
    not the intended recipient.
     
    Jeffrey E. Forcier, Jul 24, 2005
    #3
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Jeffrey E. Forcier
    Replies:
    7
    Views:
    323
    Christopher Subich
    Jul 24, 2005
  2. [david]
    Replies:
    6
    Views:
    310
    John Machin
    Sep 13, 2007
  3. netimen
    Replies:
    5
    Views:
    275
    Steven D'Aprano
    Oct 23, 2008
  4. Francis Hwang
    Replies:
    4
    Views:
    128
    Pit Capitain
    Mar 8, 2005
  5. peter
    Replies:
    0
    Views:
    137
    peter
    Dec 7, 2012
Loading...

Share This Page