That one actually makes a lot of sense to me, even if I've got to admit that I tend to go with the first option in my own code. It looks to me like a result of a couple rules that, in general, are sound: First, if an argument list can't fit all on one line, then every argument needs to go on a new line. And all the arguments need to be indented to the same level. The argument to that function includes the braces, so the opening one needs to go on a new line, and the closing one needs to be indented as well.
In this particular case, that may result in code that breaks with tradition. But I can't see a way to preserve the tradition without creating special or edge cases. For example, we can't follow the first option and get clean formatting with a function like zip. You'll have a train wreck of bad options about where to put the braces and how to place the comprehension's body in relation to the braces that enclose it. Versus, with Black's style, the answer is easy and straightforward, because you just do it the same way you would anywhere else:
zipped = zip(
{
apple.stem
for satchel in satchels
for apple in satchel
},
{
apple.core
for satchel in satchels
for apple in satchel
}
)
IMO, that's good, even if it isn't what we're all used to. Having a bunch of special cases just to match what someone might think is more aesthetically pleasing in specific situations is not a desirable feature in a set of autoformatting rules.
That works if they're both comprehensions, but starts looking more gross when the 2nd argument isn't a comprehension, and now you're looking at deciding among options like
zipped = zip({
apple.stem
for satchel in satchels
for apple in satchel
},
someList
)
or
zipped = zip({
apple.stem
for satchel in satchels
for apple in satchel
}, someList)
(Which admittedly looks reasonably tidy, but starts to get gross again if we start looking at 3-ary functions.)
You've also got to contend with the first not being a comprehension meaning that the comprehension's indenting can't so easily be kept the same:
zipped = zip(
someList,
{
apple.stem
for satchel in satchels
for apple in satchel
}
)
Which is where I was going with the comment about edge cases. Personally, I don't want formatting rules where you might decide to format the arguments to a function in different ways depending on the specifics of what other arguments the function has. I like simple. Give me one rule for when it all fits on one line, and another rule for when it doesn't. And make sure neither of the rules causes me to have to re-indent things just because a function picked up an additional argument. And make sure that the rules are completely oblivious to the function's arity.
There was a proposal a few years ago to add array comprehensions to the language, but it was ultimately rejected in favour of using .map/.filter instead.
Not sure what came about of object comprehensions though.
In this particular case, that may result in code that breaks with tradition. But I can't see a way to preserve the tradition without creating special or edge cases. For example, we can't follow the first option and get clean formatting with a function like zip. You'll have a train wreck of bad options about where to put the braces and how to place the comprehension's body in relation to the braces that enclose it. Versus, with Black's style, the answer is easy and straightforward, because you just do it the same way you would anywhere else:
IMO, that's good, even if it isn't what we're all used to. Having a bunch of special cases just to match what someone might think is more aesthetically pleasing in specific situations is not a desirable feature in a set of autoformatting rules.