### Numeric literals
-*Black* standardizes all numeric literals to use lowercase letters: `0xab`
-instead of `0XAB` and `1e10` instead of `1E10`. In Python 3.6+, *Black*
-adds underscores to long numeric literals to aid readability: `100000000`
-becomes `100_000_000`.
+*Black* standardizes most numeric literals to use lowercase letters: `0xab`
+instead of `0XAB` and `1e10` instead of `1E10`. Python 2 long literals are
+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`.
### Line breaks & binary operators
* adjacent string literals are now correctly split into multiple lines (#463)
-* code with `_` in numeric literals is recognized as Python 3.6+ (#461)
+* numeric literals are now formatted by *Black* (#452, #461, #464, #469):
-* numeric literals are now normalized to include `_` separators on Python 3.6+ code
- (#452)
+ * numeric literals are normalized to include `_` separators on Python 3.6+ code
+
+ * code with `_` in numeric literals is recognized as Python 3.6+
+
+ * most letters in numeric literals are lowercased (e.g., in `1e10` or `0xab`)
* cache is now populated when `--check` is successful for a file which speeds up
consecutive checks of properly formatted unmodified files (#448)
def normalize_numeric_literal(leaf: Leaf, allow_underscores: bool) -> None:
"""Normalizes numeric (float, int, and complex) literals.
- All letters used in the representation are normalized to lowercase, long number
- literals are split using underscores.
+ All letters used in the representation are normalized to lowercase (except
+ in Python 2 long literals), and long number literals are split using underscores.
"""
text = leaf.value.lower()
if text.startswith(("0o", "0x", "0b")):
elif text.endswith(("j", "l")):
number = text[:-1]
suffix = text[-1]
+ # Capitalize in "2L" because "l" looks too similar to "1".
+ if suffix == "l":
+ suffix = "L"
text = f"{format_float_or_int_string(number, allow_underscores)}{suffix}"
else:
text = format_float_or_int_string(text, allow_underscores)
before, after = text.split(".")
before = format_int_string(before, allow_underscores) if before else "0"
- after = format_int_string(after, allow_underscores) if after else "0"
+ if after:
+ after = format_int_string(after, allow_underscores, count_from_end=False)
+ else:
+ after = "0"
return f"{before}.{after}"
-def format_int_string(text: str, allow_underscores: bool) -> str:
+def format_int_string(
+ text: str, allow_underscores: bool, count_from_end: bool = True
+) -> str:
"""Normalizes underscores in a string to e.g. 1_000_000.
- Input must be a string of at least six digits and optional underscores.
+ Input must be a string of digits and optional underscores.
+ If count_from_end is False, we add underscores after groups of three digits
+ counting from the beginning instead of the end of the strings. This is used
+ for the fractional part of float literals.
"""
if not allow_underscores:
return text
# No underscores for numbers <= 6 digits long.
return text
- # Avoid removing leading zeros, which are important if we're formatting
- # part of a number like "0.001".
- return format(int("1" + text), "3_")[1:].lstrip("_")
+ if count_from_end:
+ # Avoid removing leading zeros, which are important if we're formatting
+ # part of a number like "0.001".
+ return format(int("1" + text), "3_")[1:].lstrip("_")
+ else:
+ return "_".join(text[i : i + 3] for i in range(0, len(text), 3))
def normalize_invisible_parens(node: Node, parens_after: Set[str]) -> None: