27

In the python document 2.4.3. Formatted string literals, it seems possible to write a star followed by an expression in a f-string's {}, but I cannot find how to use that.

What's that and how I can use it? Is it documented somewhere?

To be exact, this is regarding "*" or_expr part of the following BNF.

f_string          ::=  (literal_char | "{{" | "}}" | replacement_field)*
replacement_field ::=  "{" f_expression ["!" conversion] [":" format_spec] "}"
f_expression      ::=  (conditional_expression | "*" or_expr)
                         ("," conditional_expression | "," "*" or_expr)* [","]
                       | yield_expression

I tried it in REPL, but it causes an error.

>>> l = [1, 2, 3]
>>> f"{l}"
'[1, 2, 3]'
>>> f"{*l}"
  File "<stdin>", line 1
SyntaxError: can't use starred expression here
Jens
  • 8,423
  • 9
  • 58
  • 78
Takahiro Kobayashi
  • 273
  • 1
  • 3
  • 6
  • 3
    Take a look here: https://realpython.com/python-f-strings/. They have a great list for new ways python supports string formatting. To be fair never seen this kind of asterisk ever before in my life. Maybe it's something new. Hope it helps. – Gaurav Mall May 01 '19 at 09:19

3 Answers3

17

There are two alternatives for f_expression: a comma separated list of or_exprs, optionally preceded by asterisks, or a single yield_expression. Note the yield_expression does not allow an asterisk.

I assume the intention was that the comma-separated list alternative is only chosen when there's at least one comma, but the grammar doesn't actually say that. I feel like the repetition operator at the end should have been a + instead of a *.

So f"{*1}" would be a syntax error because there's an asterisk, but no comma. f"{*1,*2}" is syntactically valid, but a type error because 1 and 2 aren't iterable. f"{*[1], *[2]}" is valid and acts the same as f"{1,2}". So the asterisk is allowed because it acts as the splat operator in tuples, which can be written without parentheses in f-expressions.

Note that using or_expr as the operand to * does not mean that a bitwise or-operator has to be used there - it just means that the bitwise or-operator is the first operator in the precedence-hierachy that would be allowed as an operand to *. So it's just about setting the precedence of prefix * vs. other expressions. I believe or_expr is consistently used as the operand to prefix * everywhere in the grammar (that is, everywhere where prefix * is followed by an expression as opposed to a parameter name).

sepp2k
  • 363,768
  • 54
  • 674
  • 675
  • Yes but it also allows (based on the suggestion grammar) non comma separated as well. Besides, why use `"*" or_expr` at all while `or_expr` is linked to binary bitwise expressions? – Mazdak May 01 '19 at 11:01
  • 1
    @Kasrâmvd "Yes but it also allows (based on the suggestion grammar) non comma separated as well" It allows non-comma separated expressions (via the `yield_expression` alternative), but it doesn't allow asterisks in them. It only allows asterisks if there's at least one comma. – sepp2k May 01 '19 at 11:03
  • @Kasrâmvd Note that using `or_expr` does not mean that an or-operator has to be used there - it just means that the bitwise or operator is the first operator in the precedence-hierachy that would be allowed as an operand to `*`. So it's just about setting the precedence of prefix `*` vs. infix operators. And I believe they consistently use `or_expression` as the operand to prefix `*` everywhere in the grammar. – sepp2k May 01 '19 at 11:07
  • *it just means that ...* Here's the problem. Please give references because clearly `or_expr` has nothing to do with binary bitwise operator and as I mentioned in my answer it's most likely an identifier for `expr`. – Mazdak May 01 '19 at 11:09
  • 1
    This seems to answer my question. I tested and confirmed `f"{*l, *l}"` and `f"{*l,}"` are valid f-string where `l = [1,2,3]`. The definition of `f_expression` is not perfect, but probably it's intended to be simple. – Takahiro Kobayashi May 01 '19 at 11:10
  • 1
    The comment explaining `or_expr` being used to fix the `*` in the precedence hierarchy could be incorporated in the answer. That's the major source of confusion (along with whether `f"{*<...>}"` ever makes sense, which the answer already covers). – user4815162342 May 01 '19 at 11:20
  • Can anyone point towards documentation that supports the `or_expr` explanation? I can't find it in the [f-string doc reference](https://docs.python.org/3/reference/lexical_analysis.html#f-strings) – Princess Ruthie Aug 07 '21 at 01:07
  • @PrincessRuthie Which specific part of the explanation do you want documentation for? That an `or_expr` does not necessarily have to contain an or operator? Just look at [the definition of `or_expr` for that](https://docs.python.org/3/reference/expressions.html#grammar-token-or-expr). You'll see that the first alternative does not contain an or operator. – sepp2k Aug 07 '21 at 19:57
  • @sepp2k Thank you for your help: I can't make sense of that definition. It reads to me as the definition for an `or_expr` is an `xor_expr` or an `or_expr` or an `xor_expr`. That can't possibly be how to read that. How can I learn how to read that? And how do you go from the definition of `or_expr` to "the bitwise or operator is the first operator in the precedence-hierachy that would be allowed as an operand to *'? Is this somehow clear from BNF or is it convention? – Princess Ruthie Aug 08 '21 at 23:10
  • @PrincessRuthie First note that the second `|` symbol is in quotes. So it reads "an `or_expr` is an `xor_expr` or an `or_expr`, followed by a "|", followed by an `xor_expr`". So there's two alternatives. The first one is just an `xor_expr` and the second one has three parts: an `or_expr`, the `|` operator and an `xor_expr`. As for learning to read it, any textbook or course on formal languages and/or compiler construction would cover how to read and write grammars. As would manuals of or textbooks on any parser generator tools (though those would only cover the dialect used by that generator). – sepp2k Aug 08 '21 at 23:18
  • @PrincessRuthie 'And how do you go from the definition of or_expr to "the bitwise or operator is the first operator in the precedence-hierachy that would be allowed as an operand to *"?' The rule `or_expr` can match either an application of the `|` (bitwise or) operator or any operator that comes after it in the precedence hierarchy. It can't match any other operators except when nested inside a pair of parentheses. You can verify that by going through all the rules that can be reached from `or_expr` and comparing them to a precedence table of Python's operators. ... – sepp2k Aug 08 '21 at 23:22
  • ... So since in the grammar `or_expr` is used as the operand to `*` and `or_expr` can only match those things, it follows that according to the grammar only those operators can be used as operands to `*` (without parentheses). – sepp2k Aug 08 '21 at 23:23
0

Just stumbled on this question and thought it might be due to symmetry with other related statements, for example:

a = 1,
a = (1,)
a = 1,*()
a = *[1],

all result in a being assigned to a tuple containing 1. Note however, that not including single a comma, e.g.:

return *[1]

is, instead, a SyntaxError. This mirrors behaviour in f-strings and is likely because Python "the language" is inextricably tied to CPython "the implementation".

Taking look at CPython 3.11's grammar shows that the rules pointed to by the OP don't exist in Grammar/python.gram. What exists instead is the following rule:

fstring[expr_ty]: star_expressions

saying that an fstring is just a star_expression, as could be used in various other places (e.g. by the assignments above). The rules appearing in the language docs are just an abridged version of the full grammar which defines star_expressions more generally.

Hence I'm not sure how relevant it is to separate Python from CPython here, as people often try to do. The semantics implied by a convenient change to CPython's parser were codified by Python the language.

Sam Mason
  • 15,216
  • 1
  • 41
  • 60
  • 1
    [PEP 701](https://peps.python.org/pep-0701/) discusses the rational for the pre-PEG handling of f-strings and the motivation for incorporating them into the new PEG grammar which allows the parser generator to accommodate them directly. – chepner Jul 22 '23 at 17:24
  • @chepner interesting! have just had a play, and it seems `f"{*map(int,"1"),}"` is supported (note nested double-quotes) and produces `"(1,)"` – Sam Mason Jul 22 '23 at 19:32
  • 1
    Before the PEG grammar, the entire f-string had to be recognized by the lexer, which couldn't handle nested quotes. (It used regular expressions--the real kind, IIRC, not the regexes that can handle something between context-free and context-sensitive languages). – chepner Jul 22 '23 at 20:05
-1

You can do it by casting it as string:

f"{str(*l)}"
cheesus
  • 1,111
  • 1
  • 16
  • 44
  • this is parsed via a very different part of the grammar and has very different semantics. e.g. `str(*(1,2))` is not the same as `f"{*(1,2),}"` – Sam Mason Jul 22 '23 at 20:44