0

I found this example on the internet

def format_attributes(**attributes):
    """Return a string of comma-separated key-value pairs."""
    return ", ".join(
        f"{param}: {value}"
        for param, value in attributes.items()
    )

The syntax of the param passed to the join function caught my attention because it's sort of unusual. But it works

Doing some local testing with a minimum codebase I discovered that in:

def foo(res):
    return res

print(foo(f"{s}" for s in ["bar"]))

foo's syntax is valid and res ends up being a generator. However, if I try f"{s}" for s in ["bar"] standalone (no function in between), the expression just throws a SyntaxError: invalid syntax.

How come the f-string + for loop is valid and gets converted into a generator? What's happening under the hood when invoking foo function?

These other questions uses the same syntax:

But I found no comment explaining why this happens

Turtlean
  • 579
  • 4
  • 9

3 Answers3

1

These are examples of generator expressions and don't necessarily have anything specific to f-strings or which functions you use with them.

e.g.

>>> x = [1, 2, 3, 4]
>>> sum(i%2==0 for i in x)
2

The example counts the number of even integers in the list.

You can read more about them here: https://dbader.org/blog/python-generator-expressions

navneethc
  • 1,234
  • 8
  • 17
1

The looping construct you're using is a generator expression. To write one as a stand-alone expression, you need to add parentheses around it:

genexp = (f"{s}" for s in ["bar"])

If the generator expression is the only argument to a function, you don't need double parentheses (but you do if there are other separate arguments). Contrast:

s = sum(i % 2 for i in some_sequence) # count of odd elements, no extra parentheses needed

vs:

print(*(i for i in some_sequence if i % 2), sep=",") # print odds, parens are needed this time

There's nothing special about f-string used in the generator expression in your code, any expression works the same way.

Blckknght
  • 100,903
  • 11
  • 120
  • 169
  • I marked this one as the accepted answer because it explicitly states that no parenthesis is needed when a generator is used as a single argument. Thanks! – Turtlean Dec 05 '20 at 18:45
0

The f-string has nothing to do with it.

Although a generator expression generally requires parentheses:

some_gen = (f"{s}" for s in ["bar"])
print(foo(some_gen))

the parentheses can be omitted when the generator expression is the only argument to a function call:

# These two calls are equivalent.
foo((f"{s}" for s in ["bar"]))
foo( f"{s}" for s in ["bar"] )
chepner
  • 497,756
  • 71
  • 530
  • 681