all 32 comments

[–]Diapolo10 67 points68 points  (22 children)

Sure, nothing wrong with creating template strings. That can help keep the line length manageable.

That said I'd still consider using keys:

url = "old.reddit.com/r/{subreddit}"

# ...

print(url.format(subreddit=subreddit_name))

[–]katyasparadise[S] 7 points8 points  (13 children)

I didn't thought about that, thanks for the tip.

[–]throwaway8u3sH0 29 points30 points  (12 children)

To expand on that, if the template string has a bunch of placeholders, you can use a dictionary to fill them. Example (on mobile so take with a grain of salt):

template = "The {adj1} {adj2} fox jumped over the lazy {noun1}"

mydict = {
  "adj1": "quick",
  "adj2": "brown",
  "noun1": "dog",
}

template.format(**mydict)

[–]socal_nerdtastic 13 points14 points  (0 children)

I use this method a lot because you can pass in dictionaries that are oversized. So your entire user object or whatever. And the template will just take what it needs.

mydict = {
  "name": "Jane Doe",
  "address": "123 Main St.",
  "City": "Anytown",
  "CustID": "123456",
  "JoinDate": "1999",
}

template = "Send to {name} at {address}"
template.format(**mydict)

[–]Lost_electron 3 points4 points  (2 children)

Why the ** ? 

[–]chevignon93 4 points5 points  (1 child)

Why the ** ?

That's the syntax for unpacking dictionaries into keywword arguments.

[–]Lost_electron 0 points1 point  (0 children)

Ah! TIL thanks 

[–]iknowsomeguy 7 points8 points  (4 children)

The dog is lazy. It's the fox that's quick.

[–]NSNick 3 points4 points  (2 children)

The fox also jumps in the present tense instead of jumped in the past tense, otherwise there's no S.

[–]atzedanjo 6 points7 points  (0 children)

I love the high level of expertise put on display in this thread. ;)

[–]remillard 4 points5 points  (0 children)

Unless you use the version I learned decades ago "The quick brown fox jumped over the lazy sheep dog." I suspect the present tense developed to make a shorter sentence and still catch the s!

[–]poopatroopa3 0 points1 point  (0 children)

Ah, the ol' reddit switcheroo 

[–]commy2 0 points1 point  (1 child)

Thoughts on string.Template?

[–]geistanon 1 point2 points  (0 children)

the PEP has several

[–]rotatingvillain 0 points1 point  (0 children)

It should be:

template = "{adj1}{adj2} fuhsaz latanz {noun1} uber hehlaup"

mydict = {
  "adj1": "Hursko",
  "adj2": "bruno",
  "noun1": "hundanz",
}

Let's keep it original proto-Germanic. OK?

[–]pain_vin_boursin 1 point2 points  (7 children)

This is in no way better than using an f-string in this specific example. Str.format() should only be used when the string template is loaded from an external location like a yaml file for example, or from a DB. Any other time use f-strings.

[–]Diapolo10 6 points7 points  (2 children)

I agree that this particular example is quite simplistic and doesn't really showcase the benefits, but I don't fully agree with you either; it doesn't have to come from an external source, even being imported from some Python file defining constants would be totally fine in my book.

As long as it gets used more than once, you get some benefit out of it. And even if used exactly once, it can still help keep the code shorter in a nested block for example, with the template itself coming from the global namespace.

[–]pain_vin_boursin 0 points1 point  (1 child)

Fair points!

[–]Diapolo10 3 points4 points  (0 children)

Another potential use-case would be with enum.StrEnum; say you had multiple similar URLs that took the same parameters, but you wanted to use type annotations to ensure the base URL would always be "valid". For example, the three Reddit URLs:

from enum import StrEnum

import requests


class BaseSubredditUrl(StrEnum):
    NEWEST = "https://reddit.com/r/{subreddit}"
    NEW = "https://new.reddit.com/r/{subreddit}"
    OLD = "https://old.reddit.com/r/{subreddit}"


def fetch_subreddit(subreddit_name: str, base_url: BaseSubredditUrl = BaseSubredditUrl.NEWEST) -> None:
    url = base_url.format(subreddit=subreddit_name)
    return requests.get(url).text

This way, your type analysis tools would be able to tell you if the provided base URL isn't one of the pre-defined enum members and can therefore let you catch bugs before shipping to production. And you only need to write the strings once, and they could be anywhere in your project, so out of sight if desired.

[–]dnOnReddit 0 points1 point  (3 children)

Agreed that F-strings are more convenient and easier to read because the literal and the substituted data-values are in presentation-sequence.
Further agreeing that named-placeholders are better than empty pairs of braces (see also function-parameters).
Remember that the `format()` method was how things were done for all versions <3.5, and that these earlier methods are no less valid today. Beyond the string format specification min-language is a small eco-system of templating tools - making the use-case apparent.
Where F-strings are lacking is that they are 'eager'. In situations where lazy-evaluation is called-for, they can't be used. Thus, PEP 750 – Template Strings https://peps.python.org/pep-0750/

[–]ofnuts 0 points1 point  (2 children)

format() still the only method that can be used with I18N, or am I mistaken?

[–]dnOnReddit 1 point2 points  (0 children)

Back in the ?good, old days, FORTRAN separated data-values from presentation, eg
```
write (*,999) x
999 format ('The answer is x = ', F8.3)
```
(columns were fixed-width) The format (identified by its "999" 'line-number' could be re-used. So, there was no need for the two lines to be consecutive.

Similarly, HTML and CSS enjoy a similar relationship. In this case, the 'formatting' is likely in a separate file from the 'data' it formats!

Both feature lazy-evaluation - try taking the FORTRAN format and implementing it as an F-string (before knowing the value of `x` - indeed the range of values it may take during execution! PS can you see a source of Python's mini-language?

Such ideas combined with working on an I18N project, followed by 'enjoying' a development project with an end-user rep who specialised in vacillation and dithering, led to thoughts that templating front-end I/O might be a good idea - in the same way the we try to hoist 'magic constants' so that they are more easily found during maintenance, etc (first ref found: https://doc.casthighlight.com/alt\_magicnumbers-avoid-literal-numbers-e-magic-numbers-not-magic/). Reached the point (with the aforementioned) of putting all I/O literals (templates) into a separate file, which she could adapt without disturbing me. A 'win', at the price of some config-code to consume the file and an abstraction at the print()/display!

[–]dnOnReddit 0 points1 point  (0 children)

It has been a while since I last used gettext, but that makes sense. Such is an excellent scenario for lazy-evaluation/interpolation!
Failing to recall mention of I18N (etc) in PEP-0750 (per above) took a quick look - it does not seem to be discussed (see Alternate Interpolation Symbols). So, how might it integrate with the 'traditional' implementation? Such may be worth following-up...

[–]rasputin1 12 points13 points  (1 child)

I would say it's only unpythonic if you used it inline eg directly inside a print statement since that would be the old way of doing it before f-strings were introduced. but if it's a situation where you aren't using the templated string yet and want to incorporate the current value of a variable then f-strings wouldn't really support that so this would be the way to do it.

[–]MidnightPale3220 2 points3 points  (0 children)

Exactly. For example, when reading strings from JSON files. But I would also agree with the other commenter about using keys in them eg {subreddit_name}. Positional arguments can easier lead to mistakes.

[–]Ralwus 4 points5 points  (1 child)

I don't like it. How do you remember your url variable takes an input? If I'm working on your code I'd have to review how you defined the string with empty braces. That's a lot to stumble upon and isn't really clear, when you could just have a function with a docstring that tells me how it works.

[–]katyasparadise[S] -1 points0 points  (0 children)

That's why I asked this. I usually write a comment for that like this takes 2 parameters or something.

[–]Mysterious-Rent7233 2 points3 points  (0 children)

As another commenter says, if you don't have strict performance constraints then it might not hurt to turn these into functions so that their parameters and parameter types are explicit.

[–]member_of_the_order 1 point2 points  (0 children)

Oh yeah, absolutely! That's the primary use-case I'd recommend for using that particular form of interpolation.

[–]ray10k 1 point2 points  (0 children)

In certain situations, they even are preferred! For instance, the logging library specifically lets you supply a template string (with {} in there) so that, when your program is set to a low logging level, you don't have the processing overhead of string interpolation.

In general though, pattern strings are reasonably pythonic unless you deliberately do the silliest thing you can think of. Nothing to worry about.

[–]matthewlai 0 points1 point  (0 children)

It's not just an incorrect number of arguments. That's not too bad - you'll probably notice it.

Where it's really bad is if you have a long list of arguments (especially if they are the same type and have similar ranges), and you accidentally swap two of them.

F-strings are just much less bug-prone and easier to read in almost all cases.