add !p to pprint.pformat() in str.format() an f-strings

Right now in str.format(), we have !s, !r, and !a to allow us to call str(), repr(), and ascii() respectively on the given expression. I'm proposing that we add a !p conversion to have pprint.pformat() be called to convert the given expression to a 'pretty' string. Calling ``` print(f"My dict: {d!p}") ``` is a lot more concise than: ``` import pprint print(f"My dict: {pprint.pformat(d)}") ``` We may even be able to have a static attribute stored to change the various default kwargs of pprint.pformat().

+1. I imagine I would use this fairly often, particularly in debugging or interactive sessions. It goes well with the magic `=` in f-strings, e.g. `print(f"{d=!p}")`. It's more useful than the others because the manual way requires an import. It's easy to learn, easy to remember, and fits naturally with the rest. On Thu, Jul 16, 2020 at 6:43 AM Charles Machalow <csm10495@gmail.com> wrote:

It should do the import for you. As was proposed: ``` print(f"My dict: {d!p}") ``` should be equivalent to ``` import pprint print(f"My dict: {pprint.pformat(d)}") ``` The import should happen in the same scope. Modifying the global namespace could be confusing. A quick test shows that adding `import pprint` to `pprint.pformat({1:2})` slows things down by about 4% on my machine, which I don't think is worth being concerned about. If the dict has 100 elements, the difference becomes too small to measure. My first instinct is that monkeypatching the pprint module should affect !p, but I see that monkeypatching builtins.repr doesn't affect !r, so I'm not sure. On Tue, Jul 21, 2020 at 8:04 PM Rob Cliffe via Python-ideas < python-ideas@python.org> wrote:

On 7/21/2020 2:54 PM, Alex Hall wrote:
If we added !p as described here, we'd need to call import every time we execute !p, because we don't know if it's been imported yet. At runtime, we'd call "pformat()" via sys.modules['pprint'] after the import. That could of course be monkey patched. But I'm still not sure this is a good idea. I can't find the issue on bpo where this was mentioned, but some of the issues that spring to mind are: - Most pprint output is multi-line. Is that a good thing with f-strings? - How to handle parameters to pprint.pformat()? - Is pprint.pformat() extensible enough? Can an object control how it is pretty printed? I'm sure there are others. As I've said before, I'd prefer a revamped "pretty formatting" library, probably using singledispatch for extensibility. But even with that, issues like parameterizing the output remain. Eric

On 21/07/2020 21:00, Eric V. Smith wrote: lines, even if the usage is not frequent. ISTM this objection has no substance.
- How to handle parameters to pprint.pformat()?
This doesn't seem insurmountable. IIUC the relevant parameters are indent, width, depth, compact, sort_dicts. They could be added after `!p` separated by some separator - let's say comma for the sake of argument - with absent parameters taking the default values. So e.g. !p2 # means indent=2 !p,60 # means width=60. Etc. If the pformat API were ever changed, adding extra parameters would not cause backward incompatibility. Removing parameters or changing their order would, but both seem extremely unlikely to me (YMMV) and I expect users of !p could live with that.
- Is pprint.pformat() extensible enough? Can an object control how it is pretty printed?
I'm not sure if I fully understand this objection. It seems to be suggesting a deficiency in the current pprint.pformat. The proposal would not change anything here.
I'm sure there are others.
Could you specify.? I'm not saying I'm +1 on the proposal. Just that there don't seem to be any insurmountable obstacles to it. My greatest unease is an implicit import. Are there other areas of Python where this happens?

On 7/21/2020 7:36 PM, Rob Cliffe wrote:
But with pprint, it's not unusual, it's common. That feels qualitatively different to me.
I can't support embedding all of these params in the format string. Format strings are hard enough to understand as it is. At this point, you'd be better off with calling pprint.pformat() explicitely. f'value={pprint.pformat(value, indent=2, width=60)}' I might be able to get behind sort of a "pprint context", like a decimal context, with a context manager to specify it.
I'm not sure. But this part doesn't really bother me. Maybe it should, and it would be another reason not to move forward! Eric

On 21/07/2020 19:54, Alex Hall wrote:
from pprint import pprint ... def myfunc(): # Code modified to use !p here pprint(something) # Oops, pprint is now a module, not a function unless the import could actually be local to the f-string itself, not the surrounding scope (perhaps similar to the way a list comprehension is implemented as a function with its own scope). I don't totally understand these matters, so I may have got something wrong.

On 7/21/2020 4:59 PM, Rob Cliffe via Python-ideas wrote:
You don't need to go through globals, locals, etc. You can do the equivalent of:
Except that "sys" wouldn't be a local/global, either. But of course you could still monkeypatch pprint and break this code. There are a million ways to break code, I wouldn't worry about how the interpreter would get access to the pprint module. Instead, I'd worry about the more troubling issues which I raised in an earlier email. Those are the ones that are likely to stop this proposal. Eric

A philosophical problem with this is proposal is that it takes a notation that is processed by the bytecode compiler and makes it dependent on user code to be imported from the stdlib. We only do that in rare cases — IIRC the only other case is ‘import’ calling ‘__import__()’. This reversal of dependency is problematic because it means that core, built-in functionality could be broken by something a user could inadvertently change in the file system. Another problem I have is that pprint is kind of a second-class citizen. It’s not all that much cared for by core devs I believe, and you can’t extend it by adding a special method to a class — you have to subclass the PrettyPrinter class. All in all I don’t think this is a direction we should take. — Guido -- --Guido (mobile)

16.07.20 07:34, Charles Machalow пише:
Right now in str.format(), we have !s, !r, and !a to allow us to call str(), repr(), and ascii() respectively on the given expression.
I'm proposing that we add a !p conversion to have pprint.pformat() be called to convert the given expression to a 'pretty' string.
I do not think it is a good idea. pprint is a kind of toy module. It supports only limited set of standard collection classes and is not extensible. It does not even know anything about named tuples and dataclasses. It does not support multiline reprs. It is inconsistent in sorting set items. Its output format looks strange and is not PEP 8 compatible. It requires specifying the width.

+1. I imagine I would use this fairly often, particularly in debugging or interactive sessions. It goes well with the magic `=` in f-strings, e.g. `print(f"{d=!p}")`. It's more useful than the others because the manual way requires an import. It's easy to learn, easy to remember, and fits naturally with the rest. On Thu, Jul 16, 2020 at 6:43 AM Charles Machalow <csm10495@gmail.com> wrote:

It should do the import for you. As was proposed: ``` print(f"My dict: {d!p}") ``` should be equivalent to ``` import pprint print(f"My dict: {pprint.pformat(d)}") ``` The import should happen in the same scope. Modifying the global namespace could be confusing. A quick test shows that adding `import pprint` to `pprint.pformat({1:2})` slows things down by about 4% on my machine, which I don't think is worth being concerned about. If the dict has 100 elements, the difference becomes too small to measure. My first instinct is that monkeypatching the pprint module should affect !p, but I see that monkeypatching builtins.repr doesn't affect !r, so I'm not sure. On Tue, Jul 21, 2020 at 8:04 PM Rob Cliffe via Python-ideas < python-ideas@python.org> wrote:

On 7/21/2020 2:54 PM, Alex Hall wrote:
If we added !p as described here, we'd need to call import every time we execute !p, because we don't know if it's been imported yet. At runtime, we'd call "pformat()" via sys.modules['pprint'] after the import. That could of course be monkey patched. But I'm still not sure this is a good idea. I can't find the issue on bpo where this was mentioned, but some of the issues that spring to mind are: - Most pprint output is multi-line. Is that a good thing with f-strings? - How to handle parameters to pprint.pformat()? - Is pprint.pformat() extensible enough? Can an object control how it is pretty printed? I'm sure there are others. As I've said before, I'd prefer a revamped "pretty formatting" library, probably using singledispatch for extensibility. But even with that, issues like parameterizing the output remain. Eric

On 21/07/2020 21:00, Eric V. Smith wrote: lines, even if the usage is not frequent. ISTM this objection has no substance.
- How to handle parameters to pprint.pformat()?
This doesn't seem insurmountable. IIUC the relevant parameters are indent, width, depth, compact, sort_dicts. They could be added after `!p` separated by some separator - let's say comma for the sake of argument - with absent parameters taking the default values. So e.g. !p2 # means indent=2 !p,60 # means width=60. Etc. If the pformat API were ever changed, adding extra parameters would not cause backward incompatibility. Removing parameters or changing their order would, but both seem extremely unlikely to me (YMMV) and I expect users of !p could live with that.
- Is pprint.pformat() extensible enough? Can an object control how it is pretty printed?
I'm not sure if I fully understand this objection. It seems to be suggesting a deficiency in the current pprint.pformat. The proposal would not change anything here.
I'm sure there are others.
Could you specify.? I'm not saying I'm +1 on the proposal. Just that there don't seem to be any insurmountable obstacles to it. My greatest unease is an implicit import. Are there other areas of Python where this happens?

On 7/21/2020 7:36 PM, Rob Cliffe wrote:
But with pprint, it's not unusual, it's common. That feels qualitatively different to me.
I can't support embedding all of these params in the format string. Format strings are hard enough to understand as it is. At this point, you'd be better off with calling pprint.pformat() explicitely. f'value={pprint.pformat(value, indent=2, width=60)}' I might be able to get behind sort of a "pprint context", like a decimal context, with a context manager to specify it.
I'm not sure. But this part doesn't really bother me. Maybe it should, and it would be another reason not to move forward! Eric

On 21/07/2020 19:54, Alex Hall wrote:
from pprint import pprint ... def myfunc(): # Code modified to use !p here pprint(something) # Oops, pprint is now a module, not a function unless the import could actually be local to the f-string itself, not the surrounding scope (perhaps similar to the way a list comprehension is implemented as a function with its own scope). I don't totally understand these matters, so I may have got something wrong.

On 7/21/2020 4:59 PM, Rob Cliffe via Python-ideas wrote:
You don't need to go through globals, locals, etc. You can do the equivalent of:
Except that "sys" wouldn't be a local/global, either. But of course you could still monkeypatch pprint and break this code. There are a million ways to break code, I wouldn't worry about how the interpreter would get access to the pprint module. Instead, I'd worry about the more troubling issues which I raised in an earlier email. Those are the ones that are likely to stop this proposal. Eric

A philosophical problem with this is proposal is that it takes a notation that is processed by the bytecode compiler and makes it dependent on user code to be imported from the stdlib. We only do that in rare cases — IIRC the only other case is ‘import’ calling ‘__import__()’. This reversal of dependency is problematic because it means that core, built-in functionality could be broken by something a user could inadvertently change in the file system. Another problem I have is that pprint is kind of a second-class citizen. It’s not all that much cared for by core devs I believe, and you can’t extend it by adding a special method to a class — you have to subclass the PrettyPrinter class. All in all I don’t think this is a direction we should take. — Guido -- --Guido (mobile)

16.07.20 07:34, Charles Machalow пише:
Right now in str.format(), we have !s, !r, and !a to allow us to call str(), repr(), and ascii() respectively on the given expression.
I'm proposing that we add a !p conversion to have pprint.pformat() be called to convert the given expression to a 'pretty' string.
I do not think it is a good idea. pprint is a kind of toy module. It supports only limited set of standard collection classes and is not extensible. It does not even know anything about named tuples and dataclasses. It does not support multiline reprs. It is inconsistent in sorting set items. Its output format looks strange and is not PEP 8 compatible. It requires specifying the width.
participants (7)
-
Alex Hall
-
Charles Machalow
-
Eric V. Smith
-
Guido van Rossum
-
Jelle Zijlstra
-
Rob Cliffe
-
Serhiy Storchaka