Nicer formatting to spike Black

I’ve got an assignment I could represent as

override = (all(type(base) != meta for base in bases)
            and not any(ccccccc.get_specifications(method) for method in dddd.values()))

which would look acceptable to me, but not quite ideal.

But I’m supposed to use black- or ruff-style formatting which turns this into

override = all(type(base) != meta for base in bases) and not any(
    cccccccc.get_specifications(method) for method in dddd.values()
)

which I feel hides the logic that’s implemented.

I’d really quite like to have the line break before or after and.

I know I shouldn’t make to much of this. But is there perhaps a good solution or improvement I’m missing?

You can try splitting the complicated expression into simpler named parts. That should help keep the underlying logic apparent under formatting:

lacks_meta = all(type(base) != meta for base in bases)
found_spec = any(ccccccc.get_specifications(method) for method in dddd.values())

override = lacks_meta and not found_spec

This does however sacrifice some lazy evaluation (found_spec is now evaluated even if lacks_meta is False).

Another alternative would be to use some branching initialization:

override = True

if any(type(base) == meta for base in bases):
    override = False

if any(ccccccc.get_specifications(method) for method in dddd.values()):
    override = False

This stays just as clear under formatting, though whether this is more or less readable that the original is probably up to the reader. If you need lazy evaluation, you can prefix the if expressions with override and.

For what it’s worth, ruff and black both switch to something more like your preferred style once the number of and’d terms reaches 3 or more:

override = (
    all(type(base) != meta for base in bases)
    and not any(ccccccc.get_specifications(method) for method in dddd.values())
    and foobar()
)

You can even trick black/ruff into using this style by adding a superfluous True and[1]:

override = (
    True
    and all(type(base) != meta for base in bases)
    and not any(ccccccc.get_specifications(method) for method in dddd.values())
)

Though that will probably set off alarms in your linter. Just using # fmt: off would be clearer :wink:


  1. At least in Python 3.10+, this has virtually no runtime overhead, since True and gets optimized into a NOP ↩︎

4 Likes

You don’t have to call all and any right away.

foo = (type(base) != meta for base in bases)
bar = (ccccccc.get_specifications(method) for method in dddd.values())

override = all(foo) and not any(bar)
4 Likes

There are lots of ways of doing this that might make more or less sense in context. I expect that I would be more inclined to have something like:

if any(type(base) == meta for base in bases):
    override = False
elif any(ccccccc.get_specifications(method) for method in dddd.values()):
    override = False
else:
    override = True

I expect that I would also have this in a function though so override = False becomes return False.

In my experience the black way of reformatting long lines is never really the right approach because the good way to make those long lines more readable is never as simple as just shifting whitespace around.

2 Likes

Automatic formatters process low level patterns and cannot think at same level as you. Suggest using fmt: off and fmt: on.

1 Like

If you’re getting paid by line count, maybe do:

override = (
    all(
        type(base) != meta for base in bases
    )
    and not any(
        ccccccc.get_specifications(method) for method in dddd.values()
    )
)

Hmm, it looks like ruff and black both format that the same as the original code:

override = all(type(base) != meta for base in bases) and not any(
    cccccccc.get_specifications(method) for method in dddd.values()
)

Is the version you’re using preserving that layout under formatting? If so, which version(s)?

I think maybe you could use comments (#) to force black’s hand. Maybe something like this, but I am too lazy to try it:

override = (all(type(base) != meta for base in bases)  #
            and not any(ccccccc.get_specifications(method) for method in dddd.values()))

Ah well, black does move comments around. That seems overreaching to me.

1 Like

v2024.0.0 of the vscode autopep8 extension left it alone, that’s what I have installed. Odd that black crunches it down so much, it usually spreads things out more.