views:

541

answers:

5

Basically I want to have access to all standard python int operators, eg __and__ and __xor__ etc, specifically whenever the result is finally printed I want it represented in Hex format. (Kind of like putting my calculator into Hex mode)

class Hex(int):
  def __repr__(self):
    return "0x%x"%self
  __str__=__repr__ # this certainly helps with printing

if __name__=="__main__":
  print Hex(0x1abe11ed) ^ Hex(440720179)
  print Hex(Hex(0x1abe11ed) ^ Hex(440720179))

Ideally BOTH line of output should be hexadecimal: 0xfacade, however the first one yields decimal: 16435934

Any ideas?

A: 

Override __str__ as well.

__repr__ is used when repr(o) is called, and to display a value at the interactive prompt. __str__ is called for most instances of stringifying an object, including when it is printed.

The default __str__ behavior for an object is to fall back to the repr, but int provides its own __str__ method (which is identical to __repr__ (before Python 3), but does not fall back to __repr__).

Miles
+5  A: 

You should define __repr__ and __str__ separately:

class Hex(int):
  def __repr__(self):
    return "Hex(0x%x)" % self
  def __str__(self):
    return "0x%x" % self

The __repr__ function should (if possible) provide Python text that can be eval()uated to reconstruct the original object. On the other hand, __str__ can just return a human readable representation of the object.

Greg Hewgill
Cheers for noting the distinction between __str__ and __repr__.
Neonate
As repr(23) and str(23) give exactly the same result string, I wouldn't get particularly hot and bothered about a subclass of int also doing that -- after all if Hex is in a module you never know whether to return 'Hex(0x0)' or 'themodule.Hex(0x0)' (as you don't know how the import was expressed!) so the `eval`-ability of the string is pretty iffy anyway (`__import__('themodule').Hex(0x0)` is more widely eval'able, for modules not in subpackages, but if I saw THAT returned from `__repr__` in a code review I'd veto submitting the changeset!-).
Alex Martelli
I consider `__repr__` sort of a best-effort thing really. Obviously it can't do the right thing in all cases.
Greg Hewgill
The "best" effort (in order to be most likely eval'able) would be the above string with `__import__`, and I'd just hate to see that -- so "not quite best" is better. And since containers' `__str__` uses `__repr__` on the items, I'm leaning towards de-emphasizing the distinction.
Alex Martelli
+1  A: 

You'll need to get the operators (+, -, ** etc) to return instances of Hex. As is, it will return ints, i.e.

class Hex(int):
    def __repr__(self):
        return "Hex(0x%x)" % self
    def __str__(self):
        return "0x%x" % self
>>> h1 = Hex(100)
>>> h2 = Hex(1000)
>>> h1
Hex(0x64)
>>> h2
Hex(0x3e8)
>>> h1+h2
1100
>>> type(h1+h2)
<type 'int'>

So, you can override the various operators:

class Hex(int):
    def __repr__(self):
        return "Hex(0x%x)" % self
    def __str__(self):
        return "0x%x" % self
    def __add__(self, other):
        return Hex(super(Hex, self).__add__(other))
    def __sub__(self, other):
        return self.__add__(-other)
    def __pow__(self, power):
        return Hex(super(Hex, self).__pow__(power))
    def __xor__(self, other):
        return Hex(super(Hex, self).__xor__(other))

>>> h1 = Hex(100)
>>> h2 = Hex(1000)
>>> h1+h2
Hex(0x44c)
>>> type(h1+h2)
<class '__main__.Hex'>
>>> h1 += h2
>>> h1
Hex(0x44c)
>>> h2 ** 2
Hex(0xf4240)
>>> Hex(0x1abe11ed) ^ Hex(440720179)
>>> Hex(0xfacade)

I don't know about this, I feel that there must be a better way without having to override every operator to return an instance of Hex???

mhawke
+1  A: 

In response to your comment:

You could write a Mixin by yourself:

class IntMathMixin:
    def __add__(self, other):
        return type(self)(int(self).__add__(int(other)))
    # ... analog for the others

Then use it like this:

class Hex(IntMathMixin, int):
    def __repr__(self):
         return "0x%x"%self
    __str__=__repr__
Ralph
+3  A: 

A class decorator, especially in Python 2.6 and beyond, is the handiest way to wrap a lot of methods to "return an instance of this class's type rather than an instance of the superclass", which, as other have indicated, is your underlying issue (beyond quibbles with __str__ vs __repr__, worthwhile but not at all resolutory for your problem;-).

def returnthisclassfrom(specials):
  specialnames = ['__%s__' % s for s in specials.split()]
  def wrapit(cls, method):
    return lambda *a: cls(method(*a))
  def dowrap(cls):
    for n in specialnames:
      method = getattr(cls, n)
      setattr(cls, n, wrapit(cls, method))
    return cls
  return dowrap

@returnthisclassfrom('and or xor')
class Hex(int):
  def __repr__(self): return hex(self)
  __str__ = __repr__

a = Hex(2345)
b = Hex(5432)
print a, b, a^b

In Python 2.6, this emits

0x929 0x1538 0x1c11

as desired. Of course you can add more methodnames to the decorator, etc; if you're stuck with Python 2.5, remove the decorating line (the one starting with @) and use instead

class Hex(int):
  def __repr__(self): return hex(self)
  __str__ = __repr__
Hex = returnthisclassfrom('and or xor')(Hex)

a mite less elegant, but just as effective;-)

Edit: fixed an occurence of "the usual scoping issue" in the code.

Alex Martelli
+1. I knew there must be a simpler solution. But I wasn't aware one can use decorators on classes. Thx.
Ralph
@Ralph, you're welcome! decorator syntax is only supported since python 2.6, but I also showed how a decorator (with less elegant syntax) can be used on a class in older python (e.g. 2.5, with which you're stuck in google app engine for example, or as the system-supplied Python with MacOSX and many Linux distros, etc;-).
Alex Martelli
mhawke
Yeah, the problem is with the lambda using the last bound value of method when executed, so it will always use the last method in the `specials` list.
mhawke
Oops, the usual scoping issue, let me edit to fix!
Alex Martelli