1

I'm rolling my own Enum class for python and I'm having trouble getting __str__ and __repr__ to work correctly, what am I doing wrong?

In [2]: x = Enum(X=1, Y=2)

In [3]: x
Out[3]: common.utils.Enum

In [4]: str(x)
Out[4]: "<class 'common.utils.Enum'>"

In [5]: x.__repr__
Out[5]: <bound method type.reprfun of <class 'common.utils.Enum'>>

In [6]: x.__repr__()
Out[6]: 'Enum(Y=2, X=1)' 

The code itself:

def Enum(*args, **kwargs):
    enums = dict(zip(args, range(len(args))), **kwargs)
    def reprfun(self):
        res = 'Enum(' +\
            ', '.join(map(lambda x: '{}={}'.format(x[0],x[1]), enums.items())) +\
            ')'
        return res

    reverse = dict((value, name) for name, value in enums.items())
    typedict = enums.copy()
    typedict['name'] = reverse
    instance = type('Enum', (), typedict)
    instance.__repr__ = types.MethodType(reprfun, instance)
    instance.__str__ = types.MethodType(reprfun, instance)
    return instance
user2864740
  • 60,010
  • 15
  • 145
  • 220
piotr
  • 5,657
  • 1
  • 35
  • 60
  • 2
    You should bind `__repr__` to the class, not to the instance. – Bach Feb 19 '14 at 16:56
  • 2
    are you using the builtin Enum (http://docs.python.org/3.4/library/enum.html), a back port of this or a different implementation? - nm - i see you are rolling your own. you should look at the implementation of the actual enum class in the python source. – underrun Feb 19 '14 at 17:00

1 Answers1

7

Special methods must be added to the class, not to the instance. Any special method is always looked up on the type.

If Python didn't work like that, you couldn't use repr(ClassObj) as that'd call the ClassObj.__repr__ method which would expect self as a first argument. So Python calls type(obj).__repr__(obj) instead.

From the datamodel documentation:

For new-style classes, implicit invocations of special methods are only guaranteed to work correctly if defined on an object’s type, not in the object’s instance dictionary.

[...]

The rationale behind this behaviour lies with a number of special methods such as hash() and repr() that are implemented by all objects, including type objects. If the implicit lookup of these methods used the conventional lookup process, they would fail when invoked on the type object itself:

>>>
>>> 1 .__hash__() == hash(1)
True
>>> int.__hash__() == hash(int)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: descriptor '__hash__' of 'int' object needs an argument

The following works:

typedict = enums.copy()
typedict.update({
    'name': reverse,
    '__repr__': reprfun,
    '__str__': reprfun,
})

instance = type('Enum', (), typedict)
return instance

You do want to be careful with the naming though; instance is bound to a class object here, not an instance. The name is thus misleading. You may want to use cls or classobj or similar instead of instance.

Demo:

>>> x = Enum(X=1, Y=2)
>>> x
<class '__main__.Enum'>
>>> x.__repr__
<unbound method Enum.reprfun>
>>> x()
Enum(Y=2, X=1)
>>> str(x())
'Enum(Y=2, X=1)'
>>> repr(x())
'Enum(Y=2, X=1)'
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • I tried what you propose and still doesn't work, repr is not calling the function I provided. – piotr Feb 19 '14 at 17:25
  • 1
    @piotr: I tested and it does work. Are you certain you are creating an *instance* of the class you return? – Martijn Pieters Feb 19 '14 at 17:26
  • so what I was calling instance was the class type. Thanks a lot, can you maybe edit your answer so it's less misleading. – piotr Feb 19 '14 at 17:33
  • @piotr: It's your own naming of the type as `instance` that is misleading here. :-) I already stated that in my answer. – Martijn Pieters Feb 19 '14 at 17:37