This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: partial_format()
Type: enhancement Stage:
Components: Interpreter Core Versions: Python 3.9
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: Marco Sulla, eric.smith, serhiy.storchaka
Priority: normal Keywords:

Created on 2020-03-03 23:57 by Marco Sulla, last changed 2022-04-11 14:59 by admin.

Messages (8)
msg363317 - (view) Author: Marco Sulla (Marco Sulla) * Date: 2020-03-03 23:57
In `string` module, there's a very little known class `Template`. It implements a very simple template, but it has an interesting method: `safe_substitute()`.

`safe_substitute()` permits you to not fill the entire Template at one time. On the contrary, it substitute the placeholders that are passed, and leave the others untouched.

I think it could be useful to have a similar method for the format minilanguage. I propose a partial_format() method.

=== WHY I think this is useful? ===

This way, you can create subtemplates from a main template. You could want to use the template for creating a bunch of strings, all of them with the same value for some placeholders, but different values for other ones. This way you have *not* to reuse the same main template and substitute every time the placeholders that does not change.

`partial_format()` should act as `safe_substitute()`: if some placeholder misses a value, no error will be raised. On the contrary, the placeholder is leaved untouched.

Some example:

>>> "{} {}".partial_format(1)
'1 {}'


>>> "{x} {a}".partial_format(a="elephants")
'{x} elephants'

>>> "{:-f} and {:-f} nights".partial_format(1000)
'1000 and {:-f} nights'
msg363376 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2020-03-04 18:53
Do you have some concrete use case for this, or is this a speculative feature request?

This will do what you want, although it uses some undocumented internals of the _string module. I've only tested it a little, and I've done especially little testing for the error conditions:

from _string import formatter_parser, formatter_field_name_split

def partial_format(spec, *args, **kwargs):
    idx = 0
    auto_number = False
    manual_number = False
    result = []
    for literal_text, field_name, format_spec, conversion in formatter_parser(spec):
        result.append(literal_text)

        found = False

        if field_name is None:
            # We're at the end of the input.
            break

        if not field_name:
            # Auto-numbering fields.
            if manual_number:
                raise ValueError(
                    "cannot switch from manual field specification to automatic field numbering"
                )
            auto_number = True
            try:
                value = args[idx]
                idx += 1
                found = True
            except IndexError:
                pass
        elif isinstance(field_name, int):
            # Numbered fields.
            if auto_number:
                raise ValueError(
                    "cannot switch from automatic field number to manual field specification"
                )
            manual_number = True
            try:
                value = args[field_name]
                found = True
            except IndexError:
                pass
        else:
            # Named fields.
            try:
                value = kwargs[field_name]
                found = True
            except KeyError:
                pass

        spec = f":{format_spec}" if format_spec else ""
        conv = f"!{conversion}" if conversion else ""

        if found:
            s = f"{{{conv}{spec}}}"
            result.append(s.format(value))
        else:
            result.append(f"{{{field_name}{conv}{spec}}}")

    return "".join(result)
msg363377 - (view) Author: Marco Sulla (Marco Sulla) * Date: 2020-03-04 19:00
> Do you have some concrete use case for this?

Yes, for EWA:
https://marco-sulla.github.io/ewa/

Since it's a code generator, it uses templates a lot, and much times I feel the need for a partial substitution. In the end I solved with some ugly tricks.

Furthermore, if the method exists in the stdlib for `string.Template`, I suppose it was created because it was of some utility.
msg363378 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2020-03-04 19:02
I suggest using the version I posted here and see if it meets your needs. It uses the str.format parser to break the string into pieces, so it should be accurate as far as that goes.
msg363379 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2020-03-04 19:10
What would "{} {}".partial_format({}) return?

It is not possible to implement a "safe" variant of str.format(), because in difference to Template it can call arbitrary code and allows easily to produce arbitrary large strings. Template is more appropriate if the template came from untrusted source or if it is composed by inexperienced user.
msg363388 - (view) Author: Marco Sulla (Marco Sulla) * Date: 2020-03-04 20:43
> What would "{} {}".partial_format({}) return?
`str.partial_format()` was proposed exactly to avoid such tricks.

> It is not possible to implement a "safe" variant of str.format(),
> because in difference to Template it can call arbitrary code

If you read the documentation of `Template.safe_substitute()`, you can read also this function is not safe at all.

But Python, for example, does not implement private class attributes. Because Python is for adult and consciousness people, no?
msg363389 - (view) Author: Marco Sulla (Marco Sulla) * Date: 2020-03-04 20:44
@Eric V. Smith: that you for your effort, but I'll never use an API marked as private, that is furthermore undocumented.
msg363391 - (view) Author: Eric V. Smith (eric.smith) * (Python committer) Date: 2020-03-04 21:25
Well, I'm the one who made them private, I can make them public if they're useful. But I won't do that if you're not willing to look at it.
History
Date User Action Args
2022-04-11 14:59:27adminsetgithub: 84023
2020-03-04 21:25:26eric.smithsetmessages: + msg363391
2020-03-04 20:44:56Marco Sullasetmessages: + msg363389
2020-03-04 20:43:08Marco Sullasetmessages: + msg363388
2020-03-04 19:10:16serhiy.storchakasetnosy: + serhiy.storchaka
messages: + msg363379
2020-03-04 19:02:47eric.smithsetmessages: + msg363378
2020-03-04 19:00:03Marco Sullasetmessages: + msg363377
2020-03-04 18:53:38eric.smithsetnosy: + eric.smith
messages: + msg363376
2020-03-03 23:57:59Marco Sullacreate