On Jan 21, 2020, at 12:29, Chris Angelico <rosuav@gmail.com> wrote:
For non-dataclass classes, it would be extremely helpful to have an easy helper function available:
class Spam: def __repr__(self): return reprlib.kwargs(self, ["quality", "recipe", "ham"])
The implementation for this function would be very similar to what dataclasses already do:
# in reprlib.py def kwargs(obj, attrs): attrs = [f"{a}={getattr(obj, a)!r}" for a in attrs] return f"{obj.__class__.__qualname__}({", ".join(attrs)})"
A similar function for positional args would be equally easy.
I like this, but I think it’s still more complex than it needs to be for 80% of the cases (see below), while for the other 20%, I think it might make it too easy to get things wrong. Usually, the repr is either something you could type into the REPL to get an equal object, or something that’s a SyntaxError (usually because it’s in angle brackets). There are exceptions to that (like overlong or self-recursive containers), but as a general rule it’s true. And there’s no check here that the args in __repr__ are the same ones as in __new__/__init__, so it might be way too easy (especially when modifying code) to produce something that looks like a valid repr but isn’t—and may not even produce an error (e.g., you added a new constructor param with a default value, and didn’t add it to kwargs, so now every repr gives you a repr for something with the default value rather than the actual value). Maybe if there were a way to specify init and repr or new and repr together… but at that point you might as well use dataclasses, right?
Bikeshedding opportunity: Should it be legal to omit the attrs parameter, and have it use __slots__ or fall back to dir(obj) ?
This makes things even more dangerous. It’s very common for classes to have attributes that aren’t part of the constructor call, or constructor params that aren’t attributes, and this would give you the wrong answer. However, it might be useful to have a dump-all-attributes function that gave you an angle-bracket repr a la file objects, something like: attrstr = ' '.join([f"{obj.__class__.__qualname__} object at {id(obj):x}“] + [f"{attr} = {getattr(obj, attr)!r}" for attr in type(obj).__slots__]> return f"<{attrstr}>" It would be nice if there were a safe way to get the constructor-call-style repr. And I think there might be for 80% of the types—and the rest can specify it manually and take the risk of getting it wrong, probably. One option is the pickle/copy protocol. If the type uses one of the newargs methods, you can use that to get the constructor arguments; if it uses one of the other pickling methods (or can’t be pickled), this just doesn’t work. You could also look at the inspect.signature of __init__ and/or __new__. If every param has an attribute with the same name, use that; otherwise, this doesn’t work. And if none of the automatic ways worked and you tried to use them anyway, you get an error. But it would be nice if this error were at class-defining time rather than at repr-calling time, so maybe a decorator is actually a better solution? @reprlib.defaultrepr class Spam: