Overloading operators on the rigth.

Bengt Richter bokr at oz.net
Thu Oct 14 22:27:13 CEST 2004


On Thu, 14 Oct 2004 10:00:28 +0200, aleaxit at yahoo.com (Alex Martelli) wrote:

>denis wendum <wendum.denis at pasdepourriels.edf.fr> wrote:
>   ...
>> > Here's how to find out:
>> >
>> > >>> class foo:
>> > ...   def __getattr__(self, name):
>> > ...     print 'looking for %r' % name
>> > ...     raise AttributeError, name
>> > ...
>> > >>> f=foo()
>> > >>> 12 < f
>> > looking for '__gt__'
>> > looking for '__coerce__'
>> > looking for '__cmp__'
>   ...
>> Many thanks for your answer: you didn't just gave me the fish I asked but also
>> showed me how to use a net for catching other fish.
>
>You're welcome!  Python enjoys excellent "discoverability" (a term
>copiously illustrated, by explanation and by example, in Eric Raymond's
>excellent book "The Art of Unix Programming", highly recommended in both
>paper and free-on-the-net form) -- it shews enough of its workings,
>easily enough, that it's well worth doing some sniffing around, and the
>ability to hack together a 4-line class that displays things with some
>print statements in an interactive session is a good example.
>
>Of course, I _was_ being a tad too glib here, which helps explain...:
>
>> It seems I triggered a fierce debate with my question, but as far as I'm
>> concerned your example worked perfectly.
>
>...the "debate", which was in fact quite friendly, even for this
>community's pretty good standards.  Summarizing, I half-cheated by using
>an oldstyle class here, so that __getattr__ would indeed get called; a
>newstyle class, while having just about the same behavior, does (...to
>oversimply things a bit...) more processing at the time the class
>statement is executed, it fills up a structure of internal 'slots', so
>there is less "rooting around" at the time the < operator executes and
>less of a chance to see the wheels in motion (there are of course plenty
>of advantages in exchange, both conceptual and practical).  Moreover the
>'__coerce__' concept is being de-emphasized and deprecated, so the
>"lookup" order shifts slightly (coercion is now tried only as a "last
>ditch", after 3-way comparison with __cmp__ rather than before).
>
>This doesn't mean you can't do "sniffing around" in newstyle classes,
>but it may require a different tack, such as:
>
>>>> class foo(object):
>...   def __gt__(self, other):
>...     print 'doing gt'
>...     return NotImplemented
>...   def __cmp__(self, other):
>...     print 'doing cmp'
>...     return NotImplemented
>... 
>>>> f=foo()
>>>> 2<f
>doing gt
>doing cmp
>False
>>>> 
>
>The "return NotImplemented" may be seen as a way to implement a specific
>comparison (say __gt__) in some cases while punting to more generic ways
>implicitly -- you may use it to indicate to Python "I don't know how to
>do this specific comparison in this specific method, so please try
>something else [along the usual orders in which things are tried]"...
>
I didn't know about returning NotImplemented. That's good to know.
But otherwise the above is effectively what I wound up doing manually
in my other post, starting with all methods and manually deleting them
one by one as I retried the comparison.

Unfortunately, I am still frustrated, because there seems no way (other
than looking in sources) to discover a method whose name you don't know
ahead of time. You can just determine the order of access to known-name
methods. So I'm still looking for the magic metaclass formula that can
reveal the search for unknown methods. Maybe we need a sys._getattrhook
that could monitor operation-induced searching?

BTW, I experimented a little further with NotImplemented and the gt,cmp,coerce
methods in a base class vs derived and it looks like the search prefers
a base class gt or cmp over a derived version of the next alternative.

I wonder if a derived instance shouldn't be assumed to know more about doing
the right thing than its base class, if there is any overriding method available.
I.e., is the search really doing what it should in e.g. preferring Base.__gt__
over Derv.__cmp__ and Derv.__coerce__ ?

----< methtrack.py >---------------
class Base(object): pass
class Derv(Base): pass
for cls in (Base, Derv):
    cname = cls.__name__
    def __gt__(self, other, cname=cname):
        print 'doing %s gt' % cname
        return NotImplemented
    def __cmp__(self, other, cname=cname):
        print 'doing %s cmp' % cname
        return NotImplemented
    def __coerce__(self, other, cname=cname):
        print 'doing %s coerce' % cname
        return NotImplemented
    for m in  [__gt__, __cmp__,__coerce__]:
        setattr(cls, m.__name__, m)
 
print '-- with all:'
2 < Derv()
for cls in (Derv, Base):
    for mname in 'gt cmp coerce'.split():
        print '-- sans %s %s:' % (cls.__name__, mname)
        delattr(cls, '__%s__'%mname)
        2 < Derv()
----------------------------------------------
Results:

[13:26] C:\pywk\clp\examples>methtrack.py
-- with all:
doing Derv gt
doing Derv cmp
-- sans Derv gt:
doing Base gt
doing Derv cmp
-- sans Derv cmp:
doing Base gt
doing Base cmp
-- sans Derv coerce:
doing Base gt
doing Base cmp
-- sans Base gt:
doing Base cmp
-- sans Base cmp:
doing Base coerce
-- sans Base coerce:

Regards,
Bengt Richter



More information about the Python-list mailing list