top | item 17151813

Black: An uncompromising Python code formatter

447 points| kumaranvpl | 7 years ago |github.com

255 comments

order
[+] zestyping|7 years ago|reply
This is great except for the enforcement of double-quotes around all strings and spaces around slice operators. These two choices contradict the standard Python documentation, most of the standard library, and the behaviour of the interpreter itself.

When the language itself has an established convention, Black should follow that convention, not fight it. These two weird choices just generate needless churn, which is surprising as it seems Black has quite the opposite goal.

It's a shame, because all the other design choices in Black are pretty good!

[+] ambivalence|7 years ago|reply
Colons in slices are implemented to the letter of PEP 8. Your disagreement here probably stems from pycodestyle mistakenly enforcing a different rule (no spaces before colons on if-statements, defs, and so on) in the slice context.

The language itself doesn't have an established standard in terms of string quote usage. If it did, Black would follow it. What repr() does is a weak indicator and how the documentation is written is random, there was not only no enforcement as to which quotes to use, there wasn't even a recommendation. Black standardizes on double quotes since it has clear benefits whereas the other option does not.

[+] jsmeaton|7 years ago|reply
I have to admit that I was very excited to begin enforcing black on all of our projects at work - until the double quotes decision. I’m used to single quotes everywhere (except docstrings!).

I’m probably going to still move to black but I know I’m likely to face some pushback now on this one choice, and I don’t yet have the will to defend it.

I also know I’m being mostly unreasonable .. “why is this opinionated tool not perfectly aligned with MY opinions??” .. but feelings.

[+] kstrauser|7 years ago|reply
That's the #1 complaint I have (and hear) about Black. Everything else, I can live with. And honestly, I can live with using double quotes everywhere, especially when I can type single quotes and then let it re-write them. It's just that they currently look really, really odd to me because that's not how Python is traditionally written.
[+] waleedka|7 years ago|reply
Actually I prefer standardizing on double quotes and I do that in my code on a regular basis. I know some people use a mix, sometimes single quotes and sometimes double quotes, which is fine, but I prefer consistency in this case.
[+] fishywang|7 years ago|reply
PEP8 doesn't have a preference between single or double quotes. Black has:

> It will replace the latter with the former as long as it does not result in more backslash escapes than before.

And I think that's good enough (and in compliance with PEP8)

[+] aphextron|7 years ago|reply
Everyone has an opinion, and this is theirs. That's kind of the whole point.
[+] cup-of-tea|7 years ago|reply
Coming from a C background I always stuck with double quotes for strings. Single quotes are used for characters in C. I think double quotes are better anyway because an apostrophe is much more likely to turn up in a string than a double quote itself. I rarely have to manually escape quotes inside strings.
[+] vjeux|7 years ago|reply
At Facebook, we are now using prettier[1] on all our JavaScript files, a growing number of Hack files are formatted with Hackfmt[2] and now black is being rolled out for Python. It's a really exciting time :)

[1] https://prettier.io/ [2] https://github.com/facebook/hhvm/blob/master/hphp/hack/src/h...

[+] nickpresta|7 years ago|reply
Hey Vjeux. What does black mean for the prettier python plugin[1]? I had high hopes to move over all projects to prettier (for JS/Python). Is there going to be any merging between prettier python and black? Do you recommend one over the other?

Thanks

[1] https://github.com/prettier/plugin-python

[+] ComputerGuru|7 years ago|reply
Wow, that website is hard to use! You can’t touch scroll down on iOS unless you click towards the bottom of the page as scrolls in the animation zone are ignored.
[+] philip1209|7 years ago|reply
Why black over yapf?
[+] pomber|7 years ago|reply
Prettier is what I miss more everytime I switch from JS to Python. I hope Black fixes that.
[+] ljm|7 years ago|reply
In case of credit where credit’s due, is gofmt where the concept of auto-formatting syntax became mainstream? At first I hated it because I thought I had a style, but later on I enjoyed the consistency far more than I enjoyed my signature approach.

A programming language has an opinion on how you build software with it, so it’s appreciated that it also has an opinion on how you should write it so that it remains consistent and easy to follow. No debate about where to put braces or semicolons or whatever else doesn’t matter when putting something in front of your users.

It feels like dumbing down in a way, which is sad, but I think this is more for the benefit of collaboration than individualism or artistic intent. In that case you either disable the tool or refuse to use it.

In every other case, you’ve automated away almost every nitpick from a code review.

[+] sametmax|7 years ago|reply
When i heard about gofmt, i hated it. I wanted choice. Then i realised i loved python because it forced people to indent. I gave black a try. I still hate some style decisions, but who cares ? The benefits are far too great to pass up.
[+] platinumrad|7 years ago|reply
I think the unconfigurability of gofmt may be its greatest strength. Indent, etc. have existed for years but getting everyone to just agree on a reasonable style is invaluable. GNU Indent's default style is one that (almost) nobody uses and the number of available options is overwhelming. rustfmt's default style is fine, but it also has an overwhelming number of options, in addition to not handling line wrapping well. ocp-indent is the only other formatter I've used that's as painless as gofmt.
[+] biztos|7 years ago|reply
In the Perl world auto-formatting syntax is old hat, though of course (it being Perl) the formatter is highly configurable[0]. So in practice you get consistency within an organization, but perhaps not over time.

Coming from that background I was initially put off by the lack of options with gofmt, but once I realized the whole world of go coders would be using the same formatting I immediately fell in love with it.

[0]: https://metacpan.org/pod/distribution/Perl-Tidy/bin/perltidy

[+] iainmerrick|7 years ago|reply
Go is probably where it went mainstream in the current generation, yeah, but the idea has definitely been around for a while.

The earliest implementation I know of was COMAL, a very clean and tidy version of BASIC, I think dating from around 1980.

A lot of 80s micro BASICs did some level of auto-formatting purely as a side-effect of the source code being stored in tokenized form to save memory.

[+] Too|7 years ago|reply
Visual studio has done auto formatting of c# since at least 2005. Ive used many other tool chains that does it before go became popular. As others noted, the biggest difference with gofmt is probably the lack of configuration.
[+] cup-of-tea|7 years ago|reply
As usual the practice can be traced back a long time to Lisp. It's completely expected that Lisp code will be formatted in the standard way. This isn't a Go thing.
[+] danpalmer|7 years ago|reply
I really want to use Black, but we have a particular style point that we’d miss so much that we haven’t adopted it yet...

Double quotes for strings that need to be human readable, single quotes otherwise.

This makes it so obvious when something is going to be sent to the user, we find it really useful. That said, I think Black’s appeal is it’s uncompromising nature, so I wouldn’t ask it to change. Adding the option to turn off quote formatting would probably go against its vision. Also, it could be argued that we should use the internationalisation functions to denote strings sent to the user, but hey we don’t do i18n yet.

For now, this, and one or two places that it fails to have an opinion (number of lines after imports) are keeping us from using it.

[+] ambivalence|7 years ago|reply
Do you find your team enforcing the string quote rule consistently? It seems to me like it's easy to miss at times as automatic enforcement is impossible. Are there no cases where a string that wasn't originally planned to be user-visible ends up being so? I've heard this idea at times but when I looked at actual codebases it turns out it's more of an aspiration than an actual rule. And if you can't depend on it, why have it?

As for number of lines after imports, how is a lack of enforcement there stopping you from using the tool? Black enforces one line but is fine if you put two (on module level). In general, if you give up on the tool due to a missing rule, you end up having to manually enforce tens of other rules that you'd otherwise be free from.

[+] waleedka|7 years ago|reply
One idea you might want to consider is to create a global function, let’s call it h() and pass all your human readable strings through it, like so h(“hello world”). This function, simply returns the same string. This is more explicit than relying on quote types. It also allows you to do interesting things such as logging everything to a text file and running a spell checker on it, or checking for wrongly encoded string, ...etc.
[+] Too|7 years ago|reply
Why would you have strings that are not supposed to be human readable? Machine to machine should ideally use bytearray (b""-strings) or integer enums for that purpose.

Then you also have the ambiguity of what is considered human readable, is an xml document human readable? Http headers? File paths? Urls? Is a programmer considered human?

[+] nicpottier|7 years ago|reply
We did this as well, but I think the benefits of black and auto-formatting outweigh losing this convention.
[+] bpicolo|7 years ago|reply
Could fork it and remove that rule
[+] alexhill|7 years ago|reply
I love it. Something I always wish for with linters is an easy way to run them only for the lines changed in a particular diff, to allow a codebase to gradually converge on consistency without breaking git blame by reformatting everything. Is there a nice way to do that for any Python linter?
[+] vjeux|7 years ago|reply
At Facebook we only tell you about lint violations for the lines you touch using arcanist from phabricator[1]. While it works great for most lint warnings, this hasn't worked that well for code formatters.

The most successful strategy was to add a flag in the file (@format in the header) to tell that a file is automatically formatted. The immediate benefit is that we enable format on save for developers on those files when they use Nuclide (>90% of penetration for JavaScript and Hack).

The other advantage is that when we release a new version of the formatter, we can re-run it on all those files so that people don't have lint warnings on code they already formatted in the past.

With that setup, there's a strong incentive for individual engineers to run the formatter on their team codebase in one PR and then everyone benefits from now on.

[1] https://secure.phabricator.com/

[+] ianamartin|7 years ago|reply
I'm so glad this is a thing. I get so tired of people arguing about the small stuff. Sometimes too much freedom is a bad thing. It seems okay at first, and you try to be as democratic as you can with your team, and then someone wants something really wonky, and PEP8 and PEP257 don't say it's absolutely wrong after all, and shit just wastes time.

I don't agree with every detail--double quotes as default is going to be hard for me to adjust to--but the things I don't agree with aren't as important to me as being able to set it and forget it and stop debating it every so often.

This is the gofmt the python world needs. As far as I'm concerned this is the new standard.

Well done.

[+] kungtotte|7 years ago|reply
Even if your team is totally on board with your style guide, everyone wants to stay pep8/257 compliant, no arguments, you use linters to warn about errors, etc. you will still have some instances of people committing code that breaks the guidelines.

Either it gets caught in code review and you have to waste time with nitpicking, or worse it makes it through to the repo and now you have to make a commit to fix what amounts to a typo.

Autoformatting with a unified, consistent tool means that you remove all those problems.

[+] acdha|7 years ago|reply
> I don't agree with every detail--double quotes as default is going to be hard for me to adjust to--but the things I don't agree with aren't as important to me as being able to set it and forget it and stop debating it every so often.

That's the key part for me, too: Black offers the freedom of not needing to waste time talking about things which really don't matter. No more wasting time on code review where the real issues are obscured by sloppy whitespace, idiosyncratic formatting preferences, etc.

I also had a preference for single quotes but … every file in every project I work on is consistent as soon as I hit save and I certainly don't care enough not to let that outweigh a minor aesthetic point.

[+] curtis|7 years ago|reply
> By using it, you agree to cede control over minutiae of hand-formatting.

I may be in a minority, but I do not want to cede control over minutiae of hand-formatting. Am I the only person that feels this way?

[+] davidfstr|7 years ago|reply
Love the idea of eliminating formatting debates when writing Python.

At the risk of being redundant, I also raise an eyebrow at the choice to prefer double quotes for strings. My company standardized on single quotes, mainly to be consistent with repr and also encourage the use of double quotes in messages displayed to the user.

Everything else seems in order. I might increase the line width to 90 just to use an easier value to remember when configuring editors and other tools ;)

[+] epr|7 years ago|reply
--single-quoted-strings would be a really nice option for the vast majority of python programmers that use single quotes by default.
[+] aldanor|7 years ago|reply
Ditto, no single quoted strings -- not even going to consider it no matter how brilliant it (potentially) is.
[+] 49bc|7 years ago|reply
> You will save time and mental energy for more important matters.

Exactly why every language, from here until the end of time, should have a “go fmt” equivalent.

[+] quietbritishjim|7 years ago|reply
The problem with code formatters for Python is that you can't just break lines using whitespace; you need to insert symbols, ideally parentheses. For example, if you need to break this line of code:

     left[first][second] = right[first][second][third]
Manual breaking looks like this:

    left[first][second] = (
        ‎right[first][second]
            [third]
    )
Code formatters will produce something like the following atrocity:

    left[first][second
        ‎] = right[first][second][
        third]
Comments and strings are also unwrappable if the formatter is afraid of inserting characters.
[+] jsmeaton|7 years ago|reply
I wanted to add a bit of positivity and mention that I'm really liking how the black project is approaching problems. For example, a few people brought up fluent interfaces[0] as an issue. There were many opinions about the right and wrong thing to do, but discussion got to a very pragmatic decision I feel.

Then there were requests to add command line arguments specifically so that tools could integrate with black that were added almost immediately.

Congrats on gaining so much traction so quickly, and thanks for listening to users (when it makes sense).

[0] https://github.com/ambv/black/issues/67

[+] erikig|7 years ago|reply
I chuckled at this "If you're paid by the line of code you write, you can pass --line-length with a lower number."
[+] zaius|7 years ago|reply
Has anyone rolled this out to an existing codebase? What's the best practice? A single commit that reformats the whole codebase? How do you avoid creating merge hell?
[+] ambivalence|7 years ago|reply
You can see how this was done for Fabric, PyPA/Warehouse, and pytest.

General guidelines:

1. One commit with only the automatic formatting. Afterwards you'll be able to skip over it easily with `git hyper-blame` or `git blame $BLACK_REV^ -- $FILE`.

2. Avoid leaving open pull requests. If you do, after landing the blackening commit, blacken all pull requests, too. They shouldn't conflict then.

3. Set up enforcement with pre-commit or CI (you can run `black --check` on Travis or similar).

4. Don't forget the repo badge ;-)

[+] joobus|7 years ago|reply
Anyone know how I can get auto format on save to work in Vim with Black? I installed with Plug.

Figured it out:

`autocmd BufWritePre *.py Black`

[+] crooked-v|7 years ago|reply
88 characters per line is a weird choice. Why not 90?
[+] hrez|7 years ago|reply
What's with python 3.6 requirement? - Major stable distros are at python 3.5. So I can't even try it without messing with system python or OS.