defining repr() of classes (not instances)

2020-04-11 11:24发布

问题:

I have a bunch of classes which I'm using as singletons / enums / dict keys, e. g. like this:

class Side(object): pass

class Left(Side): pass
class Right(Side): pass

def show_state(distribution):
    print "left is", distribution[Left]
    print "right is", distribution[Right]

distribution = { Left: 3, Right: 7 }
show_state(distribution)

This works fine for me. But I'm having a small issue with debug output I sometimes do. Normally I use just print for this like in print distribution in the show_state() function. I would love to have an output like:

{ Left: 3, Right: 7 }

But when I do this with these classes they are given out as something like this:

{<class '__main__.Right'>: 7, <class '__main__.Left'>: 3}

I tried to override the __repr__() method of my classes to achieve this, but when I do it only influences instances of my classes (which I never create). I tried to use @classmethod and @staticmethod but nothing worked.

I assume that what I print is a Left and therefore an instance of <type 'type'>, so I would have to override the __repr__() method of the type class which is immutable, unfortunately.

Is there any other trick I could use so that print distribution would print what I want?

Btw, according to the documentation, the __repr__() method should return something which the Python parser would turn into an equal object again; this is definitely not the case with an output like <class '__main__.Right'> but would definitely be the case with an output like Right.

回答1:

You are correct that you'd have to override the __repr__ of the type of a class; you don't need to edit type, you'd subclass type to create a new metaclass:

class SimpleRepr(type):
    def __repr__(cls):
        return cls.__name__

then use that as your class metaclass; assuming you are using Python 3:

class Side(metaclass=SimpleRepr): pass

or if you are using Python 2 still:

class Side(object):
    __metaclass__ = SimpleRepr

Subclasses of Side inherit the metaclass too:

>>> class SimpleRepr(type):
...     def __repr__(cls):
...         return cls.__name__
...
>>> class Side(metaclass=SimpleRepr): pass
...
>>> class Left(Side): pass
...
>>> class Right(Side): pass
...
>>> { Left: 3, Right: 7 }
{Left: 3, Right: 7}

However, you could just have used instances:

class Side(object):
    __slots__ = ('name',)
    def __init__(self, name):
        self.name = name
    def __repr__(self):
        return self.name

Left = Side('Left')
Right = Side('Right')


回答2:

Why not use an Enum with repr

from enum import Enum

class Side(Enum):
  Left = 'Left'
  Right = 'Right'

  def __repr__(self):
    return self.name


distribution = { Side.Left: 3, Side.Right: 7 }

print distribution  # {Right: 7, Left: 3}


回答3:

The reason you're finding this difficult is that this is a strange use of classes, really. Have you considered just having one class, Side, and having left and right as instances?

class Side(object):
    def __init__(self, direction):
        self.direction = direction

    def __repr__(self):
        return self.direction

left = Side(left)
right = Side(right)