New function `repr_args` in `pprint` or `reprlib`

During writing representation string builders, an often practice is to represent every argument from a known tuple-like structure and every keyword argument from a known dict-like structure using a variant of

arg_repr = []
arg_repr.extend(map(repr, args))
arg_repr.extend(f"{k}={v!r}" for k, v in kwargs.items())
return f"{type(self).__name__}({', '.join(arg_repr)})"

And well, I know some may disagree, but I’m pretty sure that a substantial amount of Python programmers would fall into writing this routine at some point.

Check out the following links:

This is not the only syntax you can write that in, and not all the results are on-topic to this proposal, but it seems like a pretty popular theme, which also happens to reside in the standard library, as visible above.

I’m tempted to think:

  • the cost of bringing this functionality in the stdlib to limit that boilerplate is small enough–a simple problem with a simple solution (yet multiline),
  • the benefit is high enough–it is an often repr-related problem, and the stdlib can already do some lifting for us.

Which leads me to believe it could be a nice, though not a super-important addition to pprint or reprlib.

I’m thinking copying over the existing implementation from asyncio (cpython/Lib/asyncio/format_helpers.py at 98b2ed7e239c807f379cd2bf864f372d79064aac · python/cpython · GitHub) could do the trick, maybe with a little bit of modifications, that is: parens being optional, a name akin to repr_args or whatever that maybe doesn’t strictly cause associations with being positional/keyword (no idea honestly), and a customizable callback for formatting values (default being repr).

What do you think?

5 Likes

+1. Had to write it numerous times and have seen many variants of it. And some incorrect implementations too. reprlib seems sensible.

Also, naming could be better. It is not representing args, but rather signature.

Also, it probably should be in line with recursive_repr so that repr comes after.

So maybe sig_repr / signature_repr?

I’m pretty sure it does not represent a signature.

A signature is part of a callable. We’re representing an undescribed set of arguments/keyword arguments, not parameters.

I was leaning towards pprint because reprlib’s main focus is to produce representation strings with size limits applied. repr_args has nothing to do with those limits.
pprint on the other hand aims to “provide a capability to “pretty-print” arbitrary Python data structures in a form which can be used as input to the interpreter”, which makes it far more suitable.

Yeah, you are right. It is not a signature. Then arg_repr? As per variable name in your example.

According to those definitions it would make sense, but from my experience, I intuitively go to reprlib for __repr__ utilities, and I go to pprint when I need to pretty print some big containers/structures.

I think “practicality beats purity” is somewhat applicable here.

Can be ambiguous whether it represents one argument or many, and whether to associate it with being positional (“arg” just feels positional, doesn’t it).

Maybe a slightly longer name format_args_and_kwargs as in asyncio is just good enough.

True, I’d also intuitively feel like reprlib is supposed to have all-about-repr things.

Especially since it already has recursive_repr(), which is also not contributing to the “limits” goal.

Not necessarily, but I see your point. Maybe something in the middle then. That one is very verbose and I think repr is a good word to have in it as it is a “representation of a call that would ideally reconstruct the object”.

repr_call—that one sounds tantalizing, but it takes two (three) to tango (ast.Call consists of a func in addition to args and keywords, so I think we don’t really represent a full call in that sense).

repr_args_and_kwargs is a name that simply works I guess.

I didn’t completely understand why so. I think it should also take func as an (maybe optional) arg and do:

def ???(..., func=None, module=False):
    if func is not None:
        name = func.__qualname__
        if module:
            name = func.__module__ + '.' + name

So that it could be a complete __repr__ if needed.

But idk, something is off to me about naming it call, just not intuitive.