Black: Long arrays wrapped unnecessarily on multiple lines

Created on 22 Feb 2020  路  12Comments  路  Source: psf/black

Versioning
Black 19.10b0, OS Windows 10 1803 and Python 3.7.2.

Is your feature request related to a problem? Please describe.
The problem is related to how black currently wraps arrays. For instance, smaller arrays such as [1,2,3] formats correctly to [1, 2, 3] while staying on a single line. Longer arrays, such as
image
becomes
image
Which uses too many lines. Note how a comma , is automatically added at the end of the array, thus likely triggering a phenomenon of this type. Removing the comma changes nothing, as it simply comes back. See ie. the black playground.

Describe the solution you'd like. I wish to use black for formatting, but with wrapping behavior similar to (or equal to) that of autopep8's formatting for such arrays as described. Which gives:
image

Describe alternatives you've considered

  • Use # fmt: off before a code block, and # fmt: on after. While this works, the array a) remains unformatted and b) stays only on one line that introduces a scrollbar which must be used to check the full content
  • Use Excel file to store longer numbered arrays, and importing them with ie. pandas to keep data entry on 1-2 lines. I find this option inconvenient for arrays of intermediate lengths (such as the longer array displayed) that a) does not fit on a single line and b) are too short for it to make sense to store it in an Excel file (ie. when setting up test arrays on-the-fly)
  • Reverting back to version 18.4a0 appear to be equivalent to fmt effect, primarily because formatting then appears to not be applied at all. I have not explored the consequences this has with other formatting behavior not related to this test case. The next version, 18.4a1, produces the exact same behavior as the behavior of 19.10b0 (also with the extra comma at the end of the then vertically formatted array)

settings.json
C# { "python.pythonPath": ".venv\\Scripts\\python.exe", "editor.acceptSuggestionOnEnter": "off", "python.formatting.provider": "black", //"python.formatting.provider": "autopep8", "editor.formatOnSave": true, "python.formatting.blackArgs": [ "--line-length", "88" ], "python.formatting.autopep8Args": [ "--max-line-length", "88", "--experimental" ] }

enhancement

Most helpful comment

Maybe a simple threshold would be enough? Like "wrap to one line per item iif the wrap to the fewest lines needed would make the max number of items per lines less than " with N=5 or 6?

This way we would have:

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2 ,3 ,4,
           5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

with longest line containing 16 items > N, but since

long_items = ["long_item_number_1", "long_item_number_2",
              "long_item_number_3", "long_item_number_4"]

has longest line only 2 items long < N, it would becomes

long_items = [
    "long_item_number_1",
    "long_item_number_2",
    "long_item_number_3",
    "long_item_number_4",
]

instead?

All 12 comments

I kind of agree with this. I wish long lists of scalar values would just wrap instead of being expanded one item per line. But that's what fmt:on/off is for. So trying to introduce heuristics to format lists in many different ways is perhaps complexity black should not have,

Maybe a simple threshold would be enough? Like "wrap to one line per item iif the wrap to the fewest lines needed would make the max number of items per lines less than " with N=5 or 6?

This way we would have:

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2 ,3 ,4,
           5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

with longest line containing 16 items > N, but since

long_items = ["long_item_number_1", "long_item_number_2",
              "long_item_number_3", "long_item_number_4"]

has longest line only 2 items long < N, it would becomes

long_items = [
    "long_item_number_1",
    "long_item_number_2",
    "long_item_number_3",
    "long_item_number_4",
]

instead?

I'm going to try and work on this issue. My solution will split the list on the least amount of lines possible while meeting the pycodestyle standards, similar to what @iago-lito suggested.

Btw, this is not black's style

array = [1, 2, 3
         4, 5, 6
]

This is
```
array = [
1, 2, 3, 4, 5, 6
]

@PravinthR Splitting to "the least amount of lines possible" leads to transforming

long_items = [
    "long_item_number_1",
    "long_item_number_2",
    "long_item_number_3",
    "long_item_number_4",
]

into

long_items = ["long_item_number_1", "long_item_number_2",
              "long_item_number_3", "long_item_number_4"]

.. right? I think we want it the other way round :

@iago-lito good point, I forgot to add that I'd be following your idea of fewest line constraints in my implementation (where if splitting to the least amount of lines possible results in < N lines, then it will just put each item on a separate line)

I kind of like the way Black format list at the moment, but I agree that once you have a long list, it should wrap it.

In order to achieve that, I suggest adding a --list-line-length variable that indicates what is the maximum number of items that should be on each row. By default, this is set to 1 (keeping the existing behavior of Black)

For example:

Runnin black --list-line-length=8 on:

a = [1, 2, 3, 4, 5, 6, 7]
b = [1, 2, 3, 4, 5, 6, 7, 8]

Would keep it the same, but running on:

c = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

Would format it into:

c = [
   1, 2, 3, 4, 5, 6, 7, 8,
   9, 10, 11
]

What do you think?

@saroad2 Would this interact with this former suggestion? Or should only one be picked?

@iago-lito If I understand you correctly you suggest going the other way around: Wrap in one line as many elements as you can, and split to different lines if the number of elements is too small?

@saroad2 Yes. I am not sure the two ideas are completely opposite, tought :) For instance (correct if I'm wrong), with the min-max N=8, the following

long_items = [
    "item_n_1", "item_n_2", "item_n_3", "item_n_4", "item_n_5", "item_n_6", "item_n_7",
    "item_n_8", "item_n_9", "item_n10",
]

is not good, because it only has 7 items on its longest line,
so it would be reformatted to:

long_items = [
    "item_n_1",
    "item_n_2",
    "item_n_3",
    "item_n_4",
    "item_n_5",
    "item_n_6",
    "item_n_7",
    "item_n_8",
    "item_n_9",
    "item_n10",
]

if --list-line-length=1, but to

long_items = [
    "item_n_1", "item_n_2",
    "item_n_3", "item_n_4",
    "item_n_5", "item_n_6",
    "item_n_7", "item_n_8",
    "item_n_9", "item_n10",
]

if --list-line-length=2, right?

Put it another way, I see the --list-line-length option useful to decide how many items per line to have then.


I'll formulate this yet another way. Let us first distinguish between the line "length" (number of characters) and the line, say, "weight" (number of items).

  • If all items fit in one line, then keep as-is, to encourage
small_list = ["a", "b", "c"]
  • If items don't all fit in one line, then the line is too "heavy".
  • As a first attempt to solve it, naively wrap into heavy lines, i.e. as few lines as possible.
  • If the heaviest resulting line is heavy enough (N items at least), then keep as-is to encourage
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2 ,3 ,4,
           5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  • But if the heaviest resulting line is too light (less than N items long), then:
  • Naively wrap into light lines instead, i.e. as many lines as possible. And each line (but the last one) is exactly --list-line-length-long. This is to encourage either
long_items = [
    "item_n_1",
    "item_n_2",
    "item_n_3",
    "item_n_4",
    "item_n_5",
    "item_n_6",
    "item_n_7",
    "item_n_8",
    "item_n_9",
]

(by default), or

long_items = [
    "item_n_1", "item_n_2",
    "item_n_3", "item_n_4",
    "item_n_5", "item_n_6",
    "item_n_7", "item_n_8",
    "item_n_9",
]

depending on user's preferences. Of course, if --list-line-length items do not even fit on one line, forget the user set option and reduce the value until they do.

What do you think?


(As a personal position, I suspect I would always prefer --list-line-length=1 for it eases reordering. But this opinion may not be shared.)

Your breakdown of the process is awesome! It is really making sense of everything.
Hope this would give the owners a few ideas for the future.

Any updates? I think this feature is a great idea, I've been struggling with some arrays splitting into a lot of ugly lines =(

Was this page helpful?
0 / 5 - 0 ratings