Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

partial_format() #84023

Closed
Marco-Sulla mannequin opened this issue Mar 3, 2020 · 8 comments
Closed

partial_format() #84023

Marco-Sulla mannequin opened this issue Mar 3, 2020 · 8 comments
Labels
3.9 only security fixes interpreter-core (Objects, Python, Grammar, and Parser dirs) type-feature A feature request or enhancement

Comments

@Marco-Sulla
Copy link
Mannequin

Marco-Sulla mannequin commented Mar 3, 2020

BPO 39842
Nosy @ericvsmith, @serhiy-storchaka, @Marco-Sulla

Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

Show more details

GitHub fields:

assignee = None
closed_at = None
created_at = <Date 2020-03-03.23:57:59.724>
labels = ['interpreter-core', 'type-feature', '3.9']
title = 'partial_format()'
updated_at = <Date 2020-03-04.21:25:26.116>
user = 'https://github.com/Marco-Sulla'

bugs.python.org fields:

activity = <Date 2020-03-04.21:25:26.116>
actor = 'eric.smith'
assignee = 'none'
closed = False
closed_date = None
closer = None
components = ['Interpreter Core']
creation = <Date 2020-03-03.23:57:59.724>
creator = 'Marco Sulla'
dependencies = []
files = []
hgrepos = []
issue_num = 39842
keywords = []
message_count = 8.0
messages = ['363317', '363376', '363377', '363378', '363379', '363388', '363389', '363391']
nosy_count = 3.0
nosy_names = ['eric.smith', 'serhiy.storchaka', 'Marco Sulla']
pr_nums = []
priority = 'normal'
resolution = None
stage = None
status = 'open'
superseder = None
type = 'enhancement'
url = 'https://bugs.python.org/issue39842'
versions = ['Python 3.9']

@Marco-Sulla
Copy link
Mannequin Author

Marco-Sulla mannequin commented Mar 3, 2020

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'

@Marco-Sulla Marco-Sulla mannequin added 3.9 only security fixes interpreter-core (Objects, Python, Grammar, and Parser dirs) type-feature A feature request or enhancement labels Mar 3, 2020
@ericvsmith
Copy link
Member

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)

@Marco-Sulla
Copy link
Mannequin Author

Marco-Sulla mannequin commented Mar 4, 2020

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.

@ericvsmith
Copy link
Member

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.

@serhiy-storchaka
Copy link
Member

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.

@Marco-Sulla
Copy link
Mannequin Author

Marco-Sulla mannequin commented Mar 4, 2020

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?

@Marco-Sulla
Copy link
Mannequin Author

Marco-Sulla mannequin commented Mar 4, 2020

@eric V. Smith: that you for your effort, but I'll never use an API marked as private, that is furthermore undocumented.

@ericvsmith
Copy link
Member

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.

@ezio-melotti ezio-melotti transferred this issue from another repository Apr 10, 2022
@iritkatriel iritkatriel closed this as not planned Won't fix, can't repro, duplicate, stale Jun 24, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3.9 only security fixes interpreter-core (Objects, Python, Grammar, and Parser dirs) type-feature A feature request or enhancement
Projects
None yet
Development

No branches or pull requests

3 participants