Skip to content

Instantly share code, notes, and snippets.

@gimbo
Last active September 6, 2019 13:40
Show Gist options
  • Save gimbo/19f1fede170090fdf029a4f3f5c7fece to your computer and use it in GitHub Desktop.
Save gimbo/19f1fede170090fdf029a4f3f5c7fece to your computer and use it in GitHub Desktop.
An attrs extension giving fields more control over how they're rendered in an autogenerated __repr__
def attrs_flexible_repr(cls):
"""
An [attrs](http://www.attrs.org/) extension giving attributes more
flexibility about how they appear in the autogenerated `__repr__`.
Out of the box, attrs gives your class a pretty decent `__repr__` method;
you can disable this at the class level to define your own, or you can
keep it but exclude attributes by setting their `repr` field to False.
This extension adds to that by giving the attributes more control over
how they're rendered, depending on the value of `repr` field; the new
possibilities are:
* `repr` a callable - pass the field's value to the callable and render
its result as the value in the repr string.
* `repr` a tuple - assumed to be a (str, callable) pair, where the str
is the label to use for the attribute (rather than its name), and the
callable is used as described above.
(Otherwise it will just behave as usual.)
Note that if you use this extension and also specify repr=False at the
class level, that will be ignored: you _will_ get a __repr__ and any
that you define for yourself will be ignored.
Example:
>>> @attrs_flexible_repr
>>> @attr.s(repr=False) # repr=False here ignored
>>> class Example:
>>> username = attr.ib() # Rendered as normal
>>> password = attr.ib(repr=False) # Excluded from repr as normal
>>> # Next field is assumed to be a datetime.datetime
>>> time = attr.ib(repr=lambda t: t.isoformat(timespec='seconds'))
>>> content = attr.ib(repr=('Length', lambda c: len(c))) # Title tweak
>>> weird = attr.ib(repr=lambda _: False) # Always renders 'False'
>>>
>>> e = Example('me', 'xxx', datetime.datetime.now(), 'Hello there', 'Blah')
>>> e
Example(username=me, time=2018-06-15T10:52:13, Length=11, weird=False)
"""
def flexible_repr(self):
parts = []
for field in cls.__attrs_attrs__:
if not field.repr:
continue
field_name = field.name
field_repr_callable = None
field_value = getattr(self, field.name)
if callable(field.repr):
field_repr_callable = field.repr
elif isinstance(field.repr, tuple):
field_name, field_repr_callable = field.repr
if field_repr_callable:
field_value = field_repr_callable(field_value)
parts.append('{}={}'.format(field_name, field_value))
return '{}({})'.format(self.__class__.__name__, ', '.join(parts))
cls.__repr__ = flexible_repr
return cls
@gimbo
Copy link
Author

gimbo commented Sep 6, 2019

Note that attrs PR 568 makes this redundant, and is surely better implemented (see issue 212 there).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment