Add underscores to numeric literals with more than six digits (#529)
This commit is contained in:
parent
2d99573b34
commit
5f9eb9e4f7
10
README.md
10
README.md
@ -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`)
|
||||||
|
29
black.py
29
black.py
@ -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:
|
||||||
|
@ -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"
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
23
tests/data/numeric_literals_skip_underscores.py
Normal file
23
tests/data/numeric_literals_skip_underscores.py
Normal 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
|
@ -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")
|
||||||
|
Loading…
Reference in New Issue
Block a user