Add underscores to numeric literals with more than six digits (#529)

This commit is contained in:
Zsolt Dollenstein 2018-09-26 12:32:11 +01:00 committed by Łukasz Langa
parent 2d99573b34
commit 5f9eb9e4f7
8 changed files with 83 additions and 9 deletions

View File

@ -381,6 +381,11 @@ styled as `2L` instead of `2l` to avoid confusion between `l` and `1`. In
Python 3.6+, *Black* adds underscores to long numeric literals to aid Python 3.6+, *Black* adds underscores to long numeric literals to aid
readability: `100000000` becomes `100_000_000`. readability: `100000000` becomes `100_000_000`.
For regions where numerals are grouped differently (like [India](https://en.wikipedia.org/wiki/Indian_numbering_system)
and [China](https://en.wikipedia.org/wiki/Chinese_numerals#Whole_numbers)),
the `-N` or `--skip-numeric-underscore-normalization` command line option
makes *Black* preserve underscores in numeric literals.
### Line breaks & binary operators ### Line breaks & binary operators
*Black* will break a line before a binary operator when splitting a block *Black* will break a line before a binary operator when splitting a block
@ -796,6 +801,8 @@ The headers controlling how code is formatted are:
- `X-Skip-String-Normalization`: corresponds to the `--skip-string-normalization` - `X-Skip-String-Normalization`: corresponds to the `--skip-string-normalization`
command line flag. If present and its value is not the empty string, no string command line flag. If present and its value is not the empty string, no string
normalization will be performed. normalization will be performed.
- `X-Skip-Numeric-Underscore-Normalization`: corresponds to the
`--skip-numeric-underscore-normalization` command line flag.
- `X-Fast-Or-Safe`: if set to `fast`, `blackd` will act as *Black* does when - `X-Fast-Or-Safe`: if set to `fast`, `blackd` will act as *Black* does when
passed the `--fast` command line flag. passed the `--fast` command line flag.
- `X-Python-Variant`: if set to `pyi`, `blackd` will act as *Black* does when - `X-Python-Variant`: if set to `pyi`, `blackd` will act as *Black* does when
@ -926,6 +933,9 @@ More details can be found in [CONTRIBUTING](CONTRIBUTING.md).
* numeric literals are normalized to include `_` separators on Python 3.6+ code * numeric literals are normalized to include `_` separators on Python 3.6+ code
* added `--skip-numeric-underscore-normalization` to disable the above behavior and
leave numeric underscores as they were in the input
* code with `_` in numeric literals is recognized as Python 3.6+ * code with `_` in numeric literals is recognized as Python 3.6+
* most letters in numeric literals are lowercased (e.g., in `1e10` or `0xab`) * most letters in numeric literals are lowercased (e.g., in `1e10` or `0xab`)

View File

@ -115,10 +115,16 @@ class FileMode(Flag):
PYTHON36 = 1 PYTHON36 = 1
PYI = 2 PYI = 2
NO_STRING_NORMALIZATION = 4 NO_STRING_NORMALIZATION = 4
NO_NUMERIC_UNDERSCORE_NORMALIZATION = 8
@classmethod @classmethod
def from_configuration( def from_configuration(
cls, *, py36: bool, pyi: bool, skip_string_normalization: bool cls,
*,
py36: bool,
pyi: bool,
skip_string_normalization: bool,
skip_numeric_underscore_normalization: bool,
) -> "FileMode": ) -> "FileMode":
mode = cls.AUTO_DETECT mode = cls.AUTO_DETECT
if py36: if py36:
@ -127,6 +133,8 @@ def from_configuration(
mode |= cls.PYI mode |= cls.PYI
if skip_string_normalization: if skip_string_normalization:
mode |= cls.NO_STRING_NORMALIZATION mode |= cls.NO_STRING_NORMALIZATION
if skip_numeric_underscore_normalization:
mode |= cls.NO_NUMERIC_UNDERSCORE_NORMALIZATION
return mode return mode
@ -196,6 +204,12 @@ def read_pyproject_toml(
is_flag=True, is_flag=True,
help="Don't normalize string quotes or prefixes.", help="Don't normalize string quotes or prefixes.",
) )
@click.option(
"-N",
"--skip-numeric-underscore-normalization",
is_flag=True,
help="Don't normalize underscores in numeric literals.",
)
@click.option( @click.option(
"--check", "--check",
is_flag=True, is_flag=True,
@ -286,6 +300,7 @@ def main(
pyi: bool, pyi: bool,
py36: bool, py36: bool,
skip_string_normalization: bool, skip_string_normalization: bool,
skip_numeric_underscore_normalization: bool,
quiet: bool, quiet: bool,
verbose: bool, verbose: bool,
include: str, include: str,
@ -296,7 +311,10 @@ def main(
"""The uncompromising code formatter.""" """The uncompromising code formatter."""
write_back = WriteBack.from_configuration(check=check, diff=diff) write_back = WriteBack.from_configuration(check=check, diff=diff)
mode = FileMode.from_configuration( mode = FileMode.from_configuration(
py36=py36, pyi=pyi, skip_string_normalization=skip_string_normalization py36=py36,
pyi=pyi,
skip_string_normalization=skip_string_normalization,
skip_numeric_underscore_normalization=skip_numeric_underscore_normalization,
) )
if config and verbose: if config and verbose:
out(f"Using configuration from {config}.", bold=False, fg="blue") out(f"Using configuration from {config}.", bold=False, fg="blue")
@ -618,7 +636,8 @@ def format_str(
remove_u_prefix=py36 or "unicode_literals" in future_imports, remove_u_prefix=py36 or "unicode_literals" in future_imports,
is_pyi=is_pyi, is_pyi=is_pyi,
normalize_strings=normalize_strings, normalize_strings=normalize_strings,
allow_underscores=py36, allow_underscores=py36
and not bool(mode & FileMode.NO_NUMERIC_UNDERSCORE_NORMALIZATION),
) )
elt = EmptyLineTracker(is_pyi=is_pyi) elt = EmptyLineTracker(is_pyi=is_pyi)
empty_line = Line() empty_line = Line()
@ -2600,8 +2619,8 @@ def format_int_string(
return text return text
text = text.replace("_", "") text = text.replace("_", "")
if len(text) <= 6: if len(text) <= 5:
# No underscores for numbers <= 6 digits long. # No underscores for numbers <= 5 digits long.
return text return text
if count_from_end: if count_from_end:

View File

@ -14,6 +14,7 @@
LINE_LENGTH_HEADER = "X-Line-Length" LINE_LENGTH_HEADER = "X-Line-Length"
PYTHON_VARIANT_HEADER = "X-Python-Variant" PYTHON_VARIANT_HEADER = "X-Python-Variant"
SKIP_STRING_NORMALIZATION_HEADER = "X-Skip-String-Normalization" SKIP_STRING_NORMALIZATION_HEADER = "X-Skip-String-Normalization"
SKIP_NUMERIC_UNDERSCORE_NORMALIZATION_HEADER = "X-Skip-Numeric-Underscore-Normalization"
FAST_OR_SAFE_HEADER = "X-Fast-Or-Safe" FAST_OR_SAFE_HEADER = "X-Fast-Or-Safe"
@ -69,11 +70,17 @@ async def handle(request: web.Request, executor: Executor) -> web.Response:
skip_string_normalization = bool( skip_string_normalization = bool(
request.headers.get(SKIP_STRING_NORMALIZATION_HEADER, False) request.headers.get(SKIP_STRING_NORMALIZATION_HEADER, False)
) )
skip_numeric_underscore_normalization = bool(
request.headers.get(SKIP_NUMERIC_UNDERSCORE_NORMALIZATION_HEADER, False)
)
fast = False fast = False
if request.headers.get(FAST_OR_SAFE_HEADER, "safe") == "fast": if request.headers.get(FAST_OR_SAFE_HEADER, "safe") == "fast":
fast = True fast = True
mode = black.FileMode.from_configuration( mode = black.FileMode.from_configuration(
py36=py36, pyi=pyi, skip_string_normalization=skip_string_normalization py36=py36,
pyi=pyi,
skip_string_normalization=skip_string_normalization,
skip_numeric_underscore_normalization=skip_numeric_underscore_normalization,
) )
req_bytes = await request.content.read() req_bytes = await request.content.read()
charset = request.charset if request.charset is not None else "utf8" charset = request.charset if request.charset is not None else "utf8"

View File

@ -225,7 +225,7 @@ def function_signature_stress_test(number:int,no_annotation=None,text:str='defau
return text[number:-1] return text[number:-1]
# fmt: on # fmt: on
def spaces(a=1, b=(), c=[], d={}, e=True, f=-1, g=1 if False else 2, h="", i=r""): def spaces(a=1, b=(), c=[], d={}, e=True, f=-1, g=1 if False else 2, h="", i=r""):
offset = attr.ib(default=attr.Factory(lambda: _r.uniform(10000, 200000))) offset = attr.ib(default=attr.Factory(lambda: _r.uniform(10000, 200_000)))
assert task._cancel_stack[: len(old_stack)] == old_stack assert task._cancel_stack[: len(old_stack)] == old_stack

View File

@ -144,7 +144,7 @@ def function_signature_stress_test(
def spaces(a=1, b=(), c=[], d={}, e=True, f=-1, g=1 if False else 2, h="", i=r""): def spaces(a=1, b=(), c=[], d={}, e=True, f=-1, g=1 if False else 2, h="", i=r""):
offset = attr.ib(default=attr.Factory(lambda: _r.uniform(10000, 200000))) offset = attr.ib(default=attr.Factory(lambda: _r.uniform(10000, 200_000)))
assert task._cancel_stack[: len(old_stack)] == old_stack assert task._cancel_stack[: len(old_stack)] == old_stack

View File

@ -16,6 +16,8 @@
x = 0B1011 x = 0B1011
x = 0O777 x = 0O777
x = 0.000000006 x = 0.000000006
x = 10000
x = 133333
# output # output
@ -23,7 +25,7 @@
#!/usr/bin/env python3.6 #!/usr/bin/env python3.6
x = 123_456_789 x = 123_456_789
x = 123456 x = 123_456
x = 0.1 x = 0.1
x = 1.0 x = 1.0
x = 1e1 x = 1e1
@ -38,3 +40,5 @@
x = 0b1011 x = 0b1011
x = 0o777 x = 0o777
x = 0.000_000_006 x = 0.000_000_006
x = 10000
x = 133_333

View File

@ -0,0 +1,23 @@
#!/usr/bin/env python3.6
x = 123456789
x = 1_2_3_4_5_6_7
x = 1E+1
x = 0xb1acc
x = 0.00_00_006
x = 12_34_567J
x = .1_2
x = 1_2.
# output
#!/usr/bin/env python3.6
x = 123456789
x = 1_2_3_4_5_6_7
x = 1e1
x = 0xB1ACC
x = 0.00_00_006
x = 12_34_567j
x = 0.1_2
x = 1_2.0

View File

@ -410,6 +410,17 @@ def test_numeric_literals(self) -> None:
black.assert_equivalent(source, actual) black.assert_equivalent(source, actual)
black.assert_stable(source, actual, line_length=ll) black.assert_stable(source, actual, line_length=ll)
@patch("black.dump_to_file", dump_to_stderr)
def test_numeric_literals_ignoring_underscores(self) -> None:
source, expected = read_data("numeric_literals_skip_underscores")
mode = (
black.FileMode.PYTHON36 | black.FileMode.NO_NUMERIC_UNDERSCORE_NORMALIZATION
)
actual = fs(source, mode=mode)
self.assertFormatEqual(expected, actual)
black.assert_equivalent(source, actual)
black.assert_stable(source, actual, line_length=ll, mode=mode)
@patch("black.dump_to_file", dump_to_stderr) @patch("black.dump_to_file", dump_to_stderr)
def test_numeric_literals_py2(self) -> None: def test_numeric_literals_py2(self) -> None:
source, expected = read_data("numeric_literals_py2") source, expected = read_data("numeric_literals_py2")