Comparisons and sorting of a numeric class....

Ian Kelly ian.g.kelly at gmail.com
Wed Jan 14 09:31:15 CET 2015


On Tue, Jan 13, 2015 at 5:12 PM, Andrew Robinson
<andrew3 at r3dsolutions.com> wrote:
> So -- even a cursory thought shows that the information could be encoded in
> a very few lines even without an instance of a subclass:
>
> class CAllFalse():
>     @classmethod
>     def __nonzero__(Kls): return False
>
> class CPartFalse():
>     @classmethod
>     def __nonzero__(Kls): return False

This doesn't actually work.

>>> class CAllFalse(object):
...   @classmethod
...   def __nonzero__(cls): return False
...
>>> bool(CAllFalse)
True

When Python evaluates the "truthiness" of an object, it looks up the
__nonzero__ method on the object's class, not on the object itself
(same goes for all the other special dunder methods). Since CAllFalse
is itself a class, its own class (or rather, metaclass) is type [1].
Thus there's no point in using @classmethod on one of these special
methods. If you really want to proceed with this, you would have to
override the metaclass and define its __nonzero__ to return False,
like so:

>>> class CAllFalseMeta(type):
...     def __nonzero__(self): return False
...
>>> class CAllFalse(object):
...     __metaclass__ = CAllFalseMeta
...
>>> bool(CAllFalse)
False

In this code CAllFalse is an instance of CAllFalseMeta, while also
being a class itself.  But I'm not sure what the point of this is,
when you could just make CAllFalse a normal class and create an
instance of it.

[1] Actually, its metaclass is only type if you inherit from object,
which you didn't do, although in Python 2.x it must be done
explicitly. If you don't inherit from object, you get what is known as
a "classic class" instead of a new-style class. There is no reason to
use the old-style classes unless you really know what you are doing.

> class cmp():
>     lex=( CAllFalse,  CPartFalse, True )
>     @staticmethod
>     def __cmp__( left, right ):
>         if type(left) == type(right): # advanced statistical compare
>             return cmp.lex.index( right ).__cmp__( cmp.lex.index( left ) )
>         else: # legacy bool compare...
>            return right.__nonzero__().__cmp__( left.__nonzero__() )

Magic methods are generally meant to be used by the interpreter, not
called directly. The Pythonic way to write that last line would have
been:

    return cmp(bool(right), bool(left))

(Assuming of course that you rename the class to something that
doesn't shadow the cmp builtin function). This also would have
revealed sooner that your __nonzero__ method isn't actually invoked by
the interpreter.

> A full implementation, that distinguishes between legacy and probabilistic
> interpretations for the math operators could be done in several ways;
> Here's one that I think is reasonably clean.
>
> class RBool(tuple):
>     """
>     Rich compare bool.
>     A tuple which contains a bool, and a relative certainty value.
>     Whenever a RBool is compared against a bool or single valued object
> tuple,
>     it will default to legacy mode and compare only against the bool
>     However, whenever compared against another RBool, or multi-element
> tuple,
>     it will do a full rich comparison.
>     """
>     def __new__(cls, data):return super(RBool,cls).__new__(cls,data)
>     def _legacy(self,other):
>         'Resize local data for proper legacy compares as needed.'
>         try:
>             if len(other)==1: return (self[0],)  # legacy compare in tuples.
>         except TypeError: return self[0] # non iterables are a legacy
> compare.
>         return tuple(self) # A full rich non-recursive compare is warranted
>     def __eq__(self,other):return self._legacy(other) == other
>     def __ne__(self,other):return self._legacy(other) != other
>     def __lt__(self,other):return self._legacy(other) <  other
>     def __le__(self,other):return self._legacy(other) <= other
>     def __gt__(self,other):return self._legacy(other) >  other
>     def __ge__(self,other):return self._legacy(other) >= other
>     # def __cmp__( #TODO:
>     # def __nonzero__( #TODO:
>
> AllTrue   = True
> PartTrue  = RBool((False,3))
> Uncertain = RBool((False,2))
> PartFalse = RBool((False,1))
> AllFalse  = RBool((False,0))

This seems fine, but I don't get why you're messing around with
subclassing tuples. Do you really want to have boolean values that are
iterable, indexable, etc. for no reason? Just create a normal class
(or maybe subclass int, to match the normal bool class) and let your
instances contain a tuple instead of being one.



More information about the Python-list mailing list