From 5f9eb9e4f7baa35cb87eb3f8a9fed81f1195a72e Mon Sep 17 00:00:00 2001 From: Zsolt Dollenstein Date: Wed, 26 Sep 2018 12:32:11 +0100 Subject: [PATCH] Add underscores to numeric literals with more than six digits (#529) --- README.md | 10 +++++++ black.py | 29 +++++++++++++++---- blackd.py | 9 +++++- tests/data/fmtonoff.py | 2 +- tests/data/function.py | 2 +- tests/data/numeric_literals.py | 6 +++- .../data/numeric_literals_skip_underscores.py | 23 +++++++++++++++ tests/test_black.py | 11 +++++++ 8 files changed, 83 insertions(+), 9 deletions(-) create mode 100644 tests/data/numeric_literals_skip_underscores.py diff --git a/README.md b/README.md index 066ab75..b56af4a 100644 --- a/README.md +++ b/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 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 *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` command line flag. If present and its value is not the empty string, no string 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 passed the `--fast` command line flag. - `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 + * 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+ * most letters in numeric literals are lowercased (e.g., in `1e10` or `0xab`) diff --git a/black.py b/black.py index 66fc2a7..5676531 100644 --- a/black.py +++ b/black.py @@ -115,10 +115,16 @@ class FileMode(Flag): PYTHON36 = 1 PYI = 2 NO_STRING_NORMALIZATION = 4 + NO_NUMERIC_UNDERSCORE_NORMALIZATION = 8 @classmethod 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": mode = cls.AUTO_DETECT if py36: @@ -127,6 +133,8 @@ class FileMode(Flag): mode |= cls.PYI if skip_string_normalization: mode |= cls.NO_STRING_NORMALIZATION + if skip_numeric_underscore_normalization: + mode |= cls.NO_NUMERIC_UNDERSCORE_NORMALIZATION return mode @@ -196,6 +204,12 @@ def read_pyproject_toml( is_flag=True, 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( "--check", is_flag=True, @@ -286,6 +300,7 @@ def main( pyi: bool, py36: bool, skip_string_normalization: bool, + skip_numeric_underscore_normalization: bool, quiet: bool, verbose: bool, include: str, @@ -296,7 +311,10 @@ def main( """The uncompromising code formatter.""" write_back = WriteBack.from_configuration(check=check, diff=diff) 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: 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, is_pyi=is_pyi, 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) empty_line = Line() @@ -2600,8 +2619,8 @@ def format_int_string( return text text = text.replace("_", "") - if len(text) <= 6: - # No underscores for numbers <= 6 digits long. + if len(text) <= 5: + # No underscores for numbers <= 5 digits long. return text if count_from_end: diff --git a/blackd.py b/blackd.py index f2bbc8a..50614d0 100644 --- a/blackd.py +++ b/blackd.py @@ -14,6 +14,7 @@ VERSION_HEADER = "X-Protocol-Version" LINE_LENGTH_HEADER = "X-Line-Length" PYTHON_VARIANT_HEADER = "X-Python-Variant" 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" @@ -69,11 +70,17 @@ async def handle(request: web.Request, executor: Executor) -> web.Response: skip_string_normalization = bool( request.headers.get(SKIP_STRING_NORMALIZATION_HEADER, False) ) + skip_numeric_underscore_normalization = bool( + request.headers.get(SKIP_NUMERIC_UNDERSCORE_NORMALIZATION_HEADER, False) + ) fast = False if request.headers.get(FAST_OR_SAFE_HEADER, "safe") == "fast": fast = True 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() charset = request.charset if request.charset is not None else "utf8" diff --git a/tests/data/fmtonoff.py b/tests/data/fmtonoff.py index 35f2889..d3edab4 100644 --- a/tests/data/fmtonoff.py +++ b/tests/data/fmtonoff.py @@ -225,7 +225,7 @@ def function_signature_stress_test(number:int,no_annotation=None,text:str='defau return text[number:-1] # fmt: on 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 diff --git a/tests/data/function.py b/tests/data/function.py index 4754588..9e2e72f 100644 --- a/tests/data/function.py +++ b/tests/data/function.py @@ -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""): - 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 diff --git a/tests/data/numeric_literals.py b/tests/data/numeric_literals.py index 9a49dfa..0d8e345 100644 --- a/tests/data/numeric_literals.py +++ b/tests/data/numeric_literals.py @@ -16,6 +16,8 @@ x = 0XB1ACC x = 0B1011 x = 0O777 x = 0.000000006 +x = 10000 +x = 133333 # output @@ -23,7 +25,7 @@ x = 0.000000006 #!/usr/bin/env python3.6 x = 123_456_789 -x = 123456 +x = 123_456 x = 0.1 x = 1.0 x = 1e1 @@ -38,3 +40,5 @@ x = 0xB1ACC x = 0b1011 x = 0o777 x = 0.000_000_006 +x = 10000 +x = 133_333 \ No newline at end of file diff --git a/tests/data/numeric_literals_skip_underscores.py b/tests/data/numeric_literals_skip_underscores.py new file mode 100644 index 0000000..e345bb9 --- /dev/null +++ b/tests/data/numeric_literals_skip_underscores.py @@ -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 \ No newline at end of file diff --git a/tests/test_black.py b/tests/test_black.py index 6eaca98..6ff5840 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -410,6 +410,17 @@ class BlackTestCase(unittest.TestCase): black.assert_equivalent(source, actual) 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) def test_numeric_literals_py2(self) -> None: source, expected = read_data("numeric_literals_py2") -- 2.39.2