How can I choose a custom string representation for a class itself (not instances of the class)?
Asked Answered
I

7

263

Consider this class:

class foo(object):
    pass

The default string representation looks something like this:

>>> str(foo)
"<class '__main__.foo'>"

How can I make this display a custom string?


See How to print instances of a class using print()? for the corresponding question about instances of the class.

In fact, this question is really a special case of that one - because in Python, classes are themselves also objects belonging to their own class - but it's not directly obvious how to apply the advice, since the default "class of classes" is pre-defined.

Ironclad answered 8/2, 2011 at 11:27 Comment(0)
G
345

Implement __str__() or __repr__() in the class's metaclass.

class MC(type):
  def __repr__(self):
    return 'Wahaha!'

class C(object):
  __metaclass__ = MC

print(C)

Use __str__ if you mean a readable stringification, use __repr__ for unambiguous representations.

Edit: Python 3 Version

class MC(type):
  def __repr__(self):
    return 'Wahaha!'

class C(object, metaclass=MC):
    pass


print(C)
Gardy answered 8/2, 2011 at 11:30 Comment(7)
I would like to create some kind of class decorator, so I can easily set custom string representations for my classes without having to write a metaclass for each of them. I am not very familiar with Python's metaclasses, so can you give me any pointers there?Isoelectronic
Unfortunately this cannot be done with class decorators; it must be set when the class is defined.Gardy
@Space_C0wb0y: You could add a string like _representation to the class body and return self._representation in the __repr__() method of the metaclass.Escadrille
@BjörnPollex You might be able to pull this of with decorator, but I'd expect you have to struggle with a lot of subtleties of the language. And even if you do you're still bound to use metaclass in one way or another since you don't want to use the default __repr__ to represent C. An alternative to having a _representation member is to create a metaclass factory that produces a metaclass with the proper __repr__ (this could be nice if you're using this a lot).Obryant
This doesn't work with Python 3, any idea how to do it?Osteoarthritis
@ThomasLeonard: https://mcmap.net/q/111039/-__metaclass__-in-python-3Gardy
So, repr(x) does not actually call x.__repr__(), but instead calls x.__class__.__repr__(x)? This seems to me a very special case in Python.Brucie
G
37
class foo(object):
    def __str__(self):
        return "representation"
    def __unicode__(self):
        return u"representation"
Gazelle answered 8/2, 2011 at 11:30 Comment(3)
This changes the string representation for instances of the class, not for the class itself.Bashibazouk
sorry, doesnt see second part of your post. Use method above.Gazelle
@RobertSiemer Why? While his answer is not specifically targeting the OP's question, it's still helpful. It helped me. And at a glance, I don't see any question asking for instance implementation. So probably people land on this page first.Kunin
A
26

If you have to choose between __repr__ or __str__ go for the first one, as by default implementation __str__ calls __repr__ when it wasn't defined.

Custom Vector3 example:

class Vector3(object):
    def __init__(self, args):
        self.x = args[0]
        self.y = args[1]
        self.z = args[2]

    def __repr__(self):
        return "Vector3([{0},{1},{2}])".format(self.x, self.y, self.z)

    def __str__(self):
        return "x: {0}, y: {1}, z: {2}".format(self.x, self.y, self.z)

In this example, repr returns again a string that can be directly consumed/executed, whereas str is more useful as a debug output.

v = Vector3([1,2,3])
print repr(v)    #Vector3([1,2,3])
print str(v)     #x:1, y:2, z:3
Assault answered 12/1, 2018 at 8:52 Comment(3)
While your point about __repr__ vs __str__ is correct, this doesn't answer the actual question, which is about class-objects, not instances.Isoelectronic
Thanks for the feedback, completely overseen that. Let me review my answer.Assault
I think you implementations for repr and str are swapped.Vincenza
P
10

Ignacio Vazquez-Abrams' approved answer is quite right. It is, however, from the Python 2 generation. An update for the now-current Python 3 would be:

class MC(type):
  def __repr__(self):
    return 'Wahaha!'

class C(object, metaclass=MC):
    pass

print(C)

If you want code that runs across both Python 2 and Python 3, the six module has you covered:

from __future__ import print_function
from six import with_metaclass

class MC(type):
  def __repr__(self):
    return 'Wahaha!'

class C(with_metaclass(MC)):
    pass

print(C)

Finally, if you have one class that you want to have a custom static repr, the class-based approach above works great. But if you have several, you'd have to generate a metaclass similar to MC for each, and that can get tiresome. In that case, taking your metaprogramming one step further and creating a metaclass factory makes things a bit cleaner:

from __future__ import print_function
from six import with_metaclass

def custom_class_repr(name):
    """
    Factory that returns custom metaclass with a class ``__repr__`` that
    returns ``name``.
    """
    return type('whatever', (type,), {'__repr__': lambda self: name})

class C(with_metaclass(custom_class_repr('Wahaha!'))): pass

class D(with_metaclass(custom_class_repr('Booyah!'))): pass

class E(with_metaclass(custom_class_repr('Gotcha!'))): pass

print(C, D, E)

prints:

Wahaha! Booyah! Gotcha!

Metaprogramming isn't something you generally need everyday—but when you need it, it really hits the spot!

Pernambuco answered 7/3, 2019 at 21:57 Comment(0)
B
2

Just adding to all the fine answers, my version with decoration:

from __future__ import print_function
import six

def classrep(rep):
    def decorate(cls):
        class RepMetaclass(type):
            def __repr__(self):
                return rep

        class Decorated(six.with_metaclass(RepMetaclass, cls)):
            pass

        return Decorated
    return decorate


@classrep("Wahaha!")
class C(object):
    pass

print(C)

stdout:

Wahaha!

The down sides:

  1. You can't declare C without a super class (no class C:)
  2. C instances will be instances of some strange derivation, so it's probably a good idea to add a __repr__ for the instances as well.
Bloodsucker answered 14/5, 2019 at 6:7 Comment(0)
H
1

Another answer, with:

  • decorator
  • types (so you keep auto-complete in IDEs)
  • works as of v3.10
import typing


class ClassReprMeta(type):
    def __repr__(self):
        attrs_str = ", ".join(
            f"{key}={getattr(self, key)}"
            for key in dir(self)
            if not key.startswith("_")
        )

        return f"{self.__name__}({attrs_str})"


T = typing.TypeVar("T")


def printable_class(cls: T) -> T:
    """Decorator to make a class object printable"""
    return ClassReprMeta(cls.__name__, cls.__bases__, dict(cls.__dict__))


@printable_class
class CONFIG:
    FIRST = 1
    SECOND = 2


print(CONFIG)  # CONFIG(FIRST=1, SECOND=2)
Huba answered 19/12, 2022 at 20:39 Comment(2)
Q: Is there a way to coerce the value to be More string-like ... for the case of passing as a Key into dictionary: d[CONFIG] => KeyError. EXAMPLE, building on yours @David: d = {"CONFIG": 456};print(d[str(CONFIG)]);print(d[CONFIG]) Run this. The str() version is ok, and the next excepts, so I currently workaround and do d[str(CONFIG)]. I am just hacking my way and wonder if there is a better way to represent nested, name-checked symbols mapped to strings (which sometimes differ from the names in my case).Navarino
I'm not sure, but remember that dictionary keys don't need to be strings, they can be anything hashable (e.g. tuples). So perhaps just a tuple of your config values will work as a key.Huba
L
0

Because you need a metaclass to do this, but you need the metaclass itself to have a parameter, you can do it with a metaclass that captures the name via lexical scope.

I find this a bit easier to read / follow than some of the alternatives.


class type_: pass

def create_type(name):
    # we do this so that we can print the class type out
    # otherwise we must instantiate it to get a proper print out
    class type_metaclass(type):
        def __repr__(self):
            return f'<{name}>'

    class actual_type(type_, metaclass=type_metaclass):
        pass
    return actual_type

my_type = create_type('my_type')

print(my_type)
# prints "<my_type>"
Luz answered 8/9, 2022 at 7:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.