# %-style formatting method

**URL:** <https://discuss.python.org/t/style-formatting-method/90215>\
**Category:** Ideas\
**Created:** [April 30, 2025, 1:47pm UTC](https://discuss.python.org/t/style-formatting-method/90215 "2025-04-30T13:47:23Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![timhoffm](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/timhoffm/32/19468_2.png) [@timhoffm](https://discuss.python.org/u/timhoffm)\
**Post date:** [April 30, 2025, 1:47pm UTC](https://discuss.python.org/t/style-formatting-method/90215/1 "2025-04-30T13:47:23Z")

</div>

Extracted as a side-topic from [Deprecate str % format operator](https://discuss.python.org/t/deprecate-str-format-operator/87015)

While %-style formatting has become much less used in favor of {}-style formatting, particularly boosted through f-strings, there are still reasonable use cases where % placeholders are better suited compared to {} placeholders (e.g. formatting TeX, dynamically creating jinja templates or regexes, where curly braces are often used literally and would need to be escaped).

The `%` formatting operator has some quirks in interpreting it’s operand: If only a single value is used, the variables can be used directly `"%s" % var` unless it’s a tuple in which case the the tuple would be unpacked and only formatting would apply to the elements inside.

I propose to add a new `str` method working on %-placeholders. Naming could be either `.pformat()` (“percent format”) to rhyme with `.format()` or `.sprintf()` to rhyme with other languages. Like `.format()` the method would support positional or keyword arguments. These would be equivalent:

```python
"spam and {}".format("ham")
"spam and %s".pformat("ham")

"spam and {food}".format(food="ham")
"spam and %(food)s".pformat(food="ham")

```

Why is this reasonable:

- One can still use %-placeholders
- One does not have to work around the quirks of the %-formatting operator
- Strong analogy to the `.format()` function, which makes it easier to learn/read.
- The % formatting operator can be hard to read (is `pattern % count` a math operation or a string formatting operation)
- The % formatting operator may be less known among newer Python users as they will primarily learn f-strings. A `.pformat()` or `.sprintf()` method is easier to look up for them.
- Implementation is simple - the method is only a handful lines lines of code when when building it on top of the operator.

Possible counter arguments:

- Its use is rare enough that we shouldn’t bother with it. - Yes it’s a tradeoff, but it has its benefits and the needed effort is limited.

---

<div class="post-metadata">

**Author:** ![jsbueno](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/jsbueno/32/1210_2.png) [@jsbueno](https://discuss.python.org/u/jsbueno)\
**Post date:** [April 30, 2025, 2:44pm UTC](https://discuss.python.org/t/style-formatting-method/90215/2 "2025-04-30T14:44:09Z")

</div>

Things to take in consideration there:  
- there are already 46 str methods. (for good or for bad - it is reasonable to argue that "if there are already 46, what would be the difference if there were 47? )  
- A callable to perform the action already exists, although may not be obvious to find - but that could be fixed with recipes in the documentations: `operator.mod(str, other)` will already work.

Also, instead of a str method, this could be placed somewhere else - string or operator modules, for example - and them it could be defined to work with strings and other types which lack the native `%` functionality, like template strings.

Personally, I’d only think about something like this in a scenario where the string transform would be passed programatically instead of hard-coded - since one of the main advantages of `%` against, sai `.format` is its compactness - and in that case, I’d naturally go to `operator.mod`.

---

<div class="post-metadata">

**Author:** ![jamestwebber](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/jamestwebber/32/12799_2.png) [@jamestwebber](https://discuss.python.org/u/jamestwebber)\
**Post date:** [April 30, 2025, 3:06pm UTC](https://discuss.python.org/t/style-formatting-method/90215/3 "2025-04-30T15:06:48Z")

</div>

> [@jsbueno](#):
>
> - A callable to perform the action already exists, although may not be obvious to find - but that could be fixed with recipes in the documentations: `operator.mod(str, other)` will already work.

Or `str. __mod__ ` for that matter. This would be a non-dunder alias for that method.

---

<div class="post-metadata">

**Author:** ![timhoffm](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/timhoffm/32/19468_2.png) [@timhoffm](https://discuss.python.org/u/timhoffm)\
**Post date:** [April 30, 2025, 4:05pm UTC](https://discuss.python.org/t/style-formatting-method/90215/4 "2025-04-30T16:05:03Z")

</div>

The point is to have an easily accessible and readable alternative to the %-operator. I think neither `operator.mod(str, other)` or `str. __mod__ (str, other)` qualify here.

---

<div class="post-metadata">

**Author:** ![Rosuav](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/rosuav/32/3429_2.png) [@Rosuav](https://discuss.python.org/u/Rosuav)\
**Post date:** [April 30, 2025, 5:51pm UTC](https://discuss.python.org/t/style-formatting-method/90215/5 "2025-04-30T17:51:27Z")

</div>

```python
def sprintf(fmt, *args):
   return fmt % args

```

This solves most of the same problems and has a cost of just two lines of code. I’ve used this a number of times.

---

<div class="post-metadata">

**Author:** ![cjdrake](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/cjdrake/32/21743_2.png) [@cjdrake](https://discuss.python.org/u/cjdrake)\
**Post date:** [April 30, 2025, 6:03pm UTC](https://discuss.python.org/t/style-formatting-method/90215/6 "2025-04-30T18:03:41Z")

</div>

Agree.

```python
operator.mod("%s %s", ("foo", "bar"))

```

is not as good as:

```python
"%s %s".pformat("foo", "bar")

```

---

<div class="post-metadata">

**Author:** ![timhoffm](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/timhoffm/32/19468_2.png) [@timhoffm](https://discuss.python.org/u/timhoffm)\
**Post date:** [April 30, 2025, 6:25pm UTC](https://discuss.python.org/t/style-formatting-method/90215/7 "2025-04-30T18:25:14Z")

</div>

> [@Rosuav](#):
>
> ```python
> def sprintf(fmt, *args):
> return fmt % args
> 
> ```
> 
> This solves most of the same problems and has a cost of just two lines of code.

Though

```python
sprintf("%s %s", "foo", "bar")

```

Is not as good as

```python
"%s %s".sprintf("foo", "bar")

```

And it’s the question how we weigh 6 lines in cpython vs two lines for (estimated) some thousand users.

---

<div class="post-metadata">

**Author:** ![Rosuav](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/rosuav/32/3429_2.png) [@Rosuav](https://discuss.python.org/u/Rosuav)\
**Post date:** [April 30, 2025, 6:35pm UTC](https://discuss.python.org/t/style-formatting-method/90215/8 "2025-04-30T18:35:28Z")

</div>

> [@timhoffm](#):
>
> Though
> 
> ```python
> sprintf("%s %s", "foo", "bar")
> 
> ```
> 
> Is not as good as
> 
> ```python
> "%s %s".sprintf("foo", "bar")
> 
> ```

I dunno, I’m used to sprintf being a function not a method (since it is one in C, and other derivatives). Not everything has to be a method. Python has things like `map` and `filter` as globals rather than as methods, and people are fine with that. The same `sprintf` function could apply equivalently to str/bytes/bytearray, which would otherwise need to be three copies of the method.

---

<div class="post-metadata">

**Author:** ![timhoffm](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/timhoffm/32/19468_2.png) [@timhoffm](https://discuss.python.org/u/timhoffm)\
**Post date:** [April 30, 2025, 7:48pm UTC](https://discuss.python.org/t/style-formatting-method/90215/9 "2025-04-30T19:48:39Z")

</div>

I’d be reluctant to give up the structural similarity to `.format()`. IMHO that’s one of the selling points.

---

<div class="post-metadata">

**Author:** ![Rosuav](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/rosuav/32/3429_2.png) [@Rosuav](https://discuss.python.org/u/Rosuav)\
**Post date:** [April 30, 2025, 9:11pm UTC](https://discuss.python.org/t/style-formatting-method/90215/10 "2025-04-30T21:11:43Z")

</div>

> [@timhoffm](#):
>
> I’d be reluctant to give up the structural similarity to `.format()`. IMHO that’s one of the selling points

That’s fair. It does mean that it has to be a language proposal, though, and can’t simply be a couple of lines at the top of your script.

---

<div class="post-metadata">

**Author:** ![NeilGirdhar](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/neilgirdhar/32/5776_2.png) [@NeilGirdhar](https://discuss.python.org/u/NeilGirdhar)\
**Post date:** [May 1, 2025, 8:15am UTC](https://discuss.python.org/t/style-formatting-method/90215/11 "2025-05-01T08:15:32Z")

</div>

> [@timhoffm](#):
>
> (e.g. formatting TeX, dynamically creating jinja templates or regexes, where curly braces are often used literally and would need to be escaped).

I think `%` should still be avoided in those cases. I wrote [a library to format complex TeX diagrams](https://github.com/NeilGirdhar/tikzify) and I just used [my own delimiters](https://github.com/NeilGirdhar/tikzify/blob/6f773132932263340bd9414afe68454be801e7d8/tikzify/_src/foundation/formatter.py#L9) for the cases in which I needed to do substitutions. In general, if you’re formatting TeX, you’re going to want to do more than just do substitutions, so you’ll need a function anyway.

Also, instead of trying to make `%` more convenient (and thereby more popular), I think it would be better to progressively act as if it doesn’t exist and find reasonable alternatives.

---

<div class="post-metadata">

**Author:** ![pekkaklarck](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/pekkaklarck/32/7211_2.png) [@pekkaklarck](https://discuss.python.org/u/pekkaklarck)\
**Post date:** [May 1, 2025, 10:40am UTC](https://discuss.python.org/t/style-formatting-method/90215/12 "2025-05-01T10:40:00Z")

</div>

This would be nice. I just did a large reformatting to our project and as part of that converted %-formatted strings to f-strings. In same cases strings themselves contained so many curly braces that it was better to keep `%s` placeholders, though. That was itself fine, but using the %-operator felt somewhat ugly and using `tmpl.pformat(...)` would have been nicer. A bigger problem is that future maintainers and contributors may not know about the footguns the %-operator has.

Another benefit of a dedicated method is that it would make it possible to recommend against using the footgun operator in the docs. Possibly it could even be deprecated in the distant future.

---

<div class="post-metadata">

**Author:** ![xitop](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/xitop/32/13242_2.png) [@xitop](https://discuss.python.org/u/xitop)\
**Post date:** [May 1, 2025, 11:16am UTC](https://discuss.python.org/t/style-formatting-method/90215/13 "2025-05-01T11:16:14Z")

</div>

Both the `.pformat` method and the `sprintf` function have an advantage over the plain `%` formatting. The `operator.mod` doesn’t have this advantage. I’m talking about passing a single value. It works fine all the time _except_ when that value happens to be a tuple:

```python
var = (1, 2, 3)

msg = "var: %s" % var # TypeError: not all arguments converted during string formatting
msg = "var: %s" % (var,) # this is the correct form

msg = operator.mod("var: %s", var) # same TypeError

def sprintf(fmt, *args):
    return fmt % args

msg = sprintf("var: %s", var) # OK

```

* * *

The `sprintf` as shown does not support mappings. It would be easy to add it, but then it would take more than just two lines:

```python
data = {'amount': 25}

msg = "var = %(amount)s" % data # OK

# sprintf as defined above
msg = sprintf("var = %(amount)s", data) # TypeError: format requires a mapping

```

* * *

I don’t have much to say about which form (a function or a method) the wrapper around the % operator should have, but I don’t doubt about its usefulness even if the sane handling of a single tuple argument would be the only real feature.

---

<div class="post-metadata">

**Author:** ![rrolls](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/rrolls/32/10842_2.png) [@rrolls](https://discuss.python.org/u/rrolls)\
**Post date:** [May 4, 2025, 11:36am UTC](https://discuss.python.org/t/style-formatting-method/90215/14 "2025-05-04T11:36:26Z")

</div>

+1 to introducing `str.pformat` exactly as described in the OP.

Calling it `sprintf` would be OK if people prefer that, though I’m more keen on the `pformat` spelling.

If `pformat` were introduced, I’d even be happy for `str. __mod__ ` to be deprecated or removed, but only once all supported Python versions contain `pformat`. (I expect this won’t happen due to the sheer amount of existing code relying on it - I’m just saying I _personally_ wouldn’t mind.)

* * *

> [@Rosuav](#):
>
> I dunno, I’m used to sprintf being a function not a method (since it is one in C, and other derivatives). Not everything has to be a method.

I’d say there is a subtle reason why this is better as a method than a function: it _clearly_ separates the format from the args.

Compare

```py
# method
'%s and %d and lots more words to make this long'.pformat(foo.abc, bar.xyz)

```

vs

```py
# free function
pformat('%s and %d and lots more words to make this long', foo.abc, bar.xyz)

```

Now although you’re _unlikely_ to pass a literal string as one of the arguments (since you’d usually just make it part of the format string instead), it’s still _valid_ to do so. So when you read the “free function” version, if your eye zones in on the `', ` immediately before `foo`, it’s not immediately clear that what is to the left of it is the format string, and not secretly an argument. When you read the “method” version, the separation is obvious, because the separator is `'.pformat(` rather than `', `.

* * *

I also personally like the bonus that one does not have to worry about the “single tuple argument” case (which has been briefly mentioned above) - that has bitten me _many_ times. I also feel that a `.pformat` method is simply more “natural” syntax than a `%` operator _because_ it might need multiple arguments. Compare

```py
'foo: %d bar' % num
'foo: %d bar, %d baz' % (num1, num2) # suddenly we need parens
'foo: %r bar' % some_tuple # WRONG
'foo: %r bar' % (some_tuple,) # correct, but not at all obvious

```

vs

```py
'foo: %d bar'.pformat(num)
'foo: %d bar, %d baz'.pformat(num1, num2) # existence of parens is consistent with above
'foo: %r bar'.pformat(*some_tuple) # wrong, but you'd never do this by accident
'foo: %r bar'.pformat(some_tuple) # RIGHT, and obvious

```

The `pformat` method is simply more consistent and more obvious, and I feel if `str. __mod__ ` had never existed and we were newly implementing this today, `str.pformat` would be the obvious choice rather than `str. __mod__ `.

---

<div class="post-metadata">

**Author:** ![Rosuav](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/rosuav/32/3429_2.png) [@Rosuav](https://discuss.python.org/u/Rosuav)\
**Post date:** [May 4, 2025, 12:31pm UTC](https://discuss.python.org/t/style-formatting-method/90215/15 "2025-05-04T12:31:31Z")

</div>

> [@rrolls](#):
>
> I’d say there is a subtle reason why this is better as a method than a function: it _clearly_ separates the format from the args.

That’s fair. I’m not sure it’s a sufficiently compelling reason to go to the effort of making it a method (which requires the agreement of the Python core devs and will only exist from version X.Y) as opposed to a stand-alone function (which can be trivially backported). Plus, the method has to be created in three places.

But yes, that is definitely a benefit.

---

<div class="post-metadata">

**Author:** ![NeilGirdhar](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/neilgirdhar/32/5776_2.png) [@NeilGirdhar](https://discuss.python.org/u/NeilGirdhar)\
**Post date:** [May 5, 2025, 7:41am UTC](https://discuss.python.org/t/style-formatting-method/90215/16 "2025-05-05T07:41:52Z")

</div>

Since the only motivation given for this feature is working with strings that have a lot of curly braces, wouldn’t it be a lot easier to support changing the delimiters of `format`? E.g.,

```python
txt = "\begin{equation}x=[price:.2f]\end{equation}!"
print(txt.format(price=49, delimiters='[]'))

```

This has the advantage of letting developers forget the antiquated %-formatting and just use one format specification everywhere.

This has the added bonus of being able to work with strings that have both curly braces and percent characters.

---

<div class="post-metadata">

**Author:** ![Rosuav](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/rosuav/32/3429_2.png) [@Rosuav](https://discuss.python.org/u/Rosuav)\
**Post date:** [May 5, 2025, 7:54am UTC](https://discuss.python.org/t/style-formatting-method/90215/17 "2025-05-05T07:54:09Z")

</div>

> [@NeilGirdhar](#):
>
> Since the only motivation given for this feature is working with strings that have a lot of curly braces,

That’s not the only motivation, there are others too. Percent formatting isn’t “antiquated” just because it’s compatible with older systems.

> [@NeilGirdhar](#):
>
> wouldn’t it be a lot easier to support changing the delimiters of `format`?

That said, though, this is a fine idea to discuss. I think it would do better standing on its own two feet than tacked onto this one. Personally, I don’t think I’d use it, but it’s the kind of thing that might well be worth discussing. Note that a simple kwarg wouldn’t work there - that already has meaning - but something else might work. Would be worth considering if it’s possible to do the same with f-strings.

I’d recommend starting a new thread for that, it has some promise.

---

<div class="post-metadata">

**Author:** ![rrolls](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/rrolls/32/10842_2.png) [@rrolls](https://discuss.python.org/u/rrolls)\
**Post date:** [May 5, 2025, 10:07am UTC](https://discuss.python.org/t/style-formatting-method/90215/18 "2025-05-05T10:07:10Z")

</div>

> [@NeilGirdhar](#):
>
> Since the only motivation given for this feature is working with strings that have a lot of curly braces
> 
> …
> 
> This has the advantage of letting developers forget the antiquated %-formatting

No it isn’t, and no it doesn’t.

`str.format` has a big downside compared to %-formatting. Which is why people happily using %-formatting today will keep using it no matter how much you try to push them to switch.

* * *

[From the docs:](https://docs.python.org/3/library/stdtypes.html#str.format)

> The string on which this method is called can contain literal text or replacement fields delimited by braces `{}`. Each replacement field contains either the numeric index of a positional argument, or the name of a keyword argument.

So when using `str.format`, you either have to think of a name for every substitution and then write it twice to pass it as a kwarg, or you have to number the substitutions and then adjust the numbering every time you alter the message.

Suppose you want to make the following code change involving a %-formatted string:

```py
# old
print('%s: starting %s' % (obj.name, obj.next))
# new
print('%s: %s done, starting %s' % (obj.name, obj.prev, obj.next))

```

As you can see, `%s done, ` was inserted along with `obj.prev, `.

With `str.format`, you either have to additionally renumber (`{1}` is changed to `{2}`):

```py
# old
print('{0}: starting {1}'.format(obj.name, obj.next))
# new
print('{0}: {1} done, starting {2}'.format(obj.name, obj.prev, obj.next))

```

or write a lot of names multiple times - which IMO makes the code _less_ clear due to the additional boilerplate:

```py
# old
print('{name}: starting {next}'.format(name=obj.name, next=obj.next))
# new
print('{name}: {prev} done, starting {next}'.format(
    name=obj.name, prev=obj.prev, next=obj.next))

```

This was a made-up, minimal example - in real-world usage with longer format strings and more arguments it makes a much bigger difference.

* * *

Yes, %-formatting has a downside too: if you need to reorder where the substitutions appear in the format string, you need to reorder the arguments as well, which limits what you can do with dynamic format strings. But that’s a rare case - and guess what? I, despite using %-formatting as my go-to, _have_ used `str.format` on the odd occasion when this was my exact use case.

They are different tools for different problems.

---

<div class="post-metadata">

**Author:** ![Rosuav](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/rosuav/32/3429_2.png) [@Rosuav](https://discuss.python.org/u/Rosuav)\
**Post date:** [May 5, 2025, 10:12am UTC](https://discuss.python.org/t/style-formatting-method/90215/19 "2025-05-05T10:12:52Z")

</div>

> [@rrolls](#):
>
> Yes, %-formatting has a downside too: if you need to reorder where the substitutions appear in the format string, you need to reorder the arguments as well, which limits what you can do with dynamic format strings.

Which, by the way, is a solvable problem. Python could lift this notation: `sprintf("%[1]s -- %[0]s", "spam", "ham")` Empty braces in `.format()` have the same effect and can similarly have a number added.

The minilanguage used by `.format()` is more verbose, the one used by printf is more compact, which means there are times you’ll want the one, and times you’ll want the other.

---

<div class="post-metadata">

**Author:** ![MegaIng](https://sea2.discourse-cdn.com/flex002/user_avatar/discuss.python.org/megaing/32/16162_2.png) [@MegaIng](https://discuss.python.org/u/MegaIng)\
**Post date:** [May 5, 2025, 10:51am UTC](https://discuss.python.org/t/style-formatting-method/90215/20 "2025-05-05T10:51:56Z")

</div>

> [@rrolls](#):
>
> So when using `str.format`, you either have to think of a name for every substitution and then write it twice to pass it as a kwarg, or you have to number the substitutions and then adjust the numbering every time you alter the message.

Or you can just not use either and get the exact same behavior as with `%s`. `'{}: {} done, starting {}'` is perfectly valid. (Which yes, isn’t explained in that quote from the docs. You need to follow the link afterwards to the full docs to learn about that.)

[Next page](https://discuss.python.org/t/style-formatting-method/90215.md?page=2)
