MartinThoma / flake8-simplify

❄ A flake8 plugin that helps you to simplify code
MIT License
185 stars 19 forks source link

[SIM117] Merge with statements for context managers that have same scope #35

Closed Skylion007 closed 3 years ago

Skylion007 commented 3 years ago

Explanation

A little known feature of Python is that a with statement can contain multiple context managers. This allows the code only one with statement (and therefore only 1 level of indentation). This rule has a similar rational to SIM102.

This rule should be applied if and only if:

Caveat when implementing: If the context names are really long, the with statement may be broken over a line break. A new feature in the Python 3.10 alpha will be to allow parentheses to be used to break the with statement over multiple lines.

Example

Consider the following context managers:

#bad
with A() as a:
    with B() as b:
        print('hello')

can be transformed into the following:

# Good
with A() as a, B() as b:
    print('hello')
MartinThoma commented 3 years ago

I love it! Should be easy to implement. I hope to find the time for it tomorrow :-)

MartinThoma commented 3 years ago
$ astpretty --no-show-offsets /dev/stdin <<< `cat example.txt`
Module(
    body=[
        With(
            items=[
                withitem(
                    context_expr=Call(
                        func=Name(id='A', ctx=Load()),
                        args=[],
                        keywords=[],
                    ),
                    optional_vars=Name(id='a', ctx=Store()),
                ),
            ],
            body=[
                With(
                    items=[
                        withitem(
                            context_expr=Call(
                                func=Name(id='B', ctx=Load()),
                                args=[],
                                keywords=[],
                            ),
                            optional_vars=Name(id='b', ctx=Store()),
                        ),
                    ],
                    body=[
                        Expr(
                            value=Call(
                                func=Name(id='print', ctx=Load()),
                                args=[Constant(value='hello', kind=None)],
                                keywords=[],
                            ),
                        ),
                    ],
                    type_comment=None,
                ),
            ],
            type_comment=None,
        ),
    ],
    type_ignores=[],
)
MartinThoma commented 3 years ago

@Skylion007 I've just added a PR which does the trick. I would also write you in the README for that rule. What do you think about that? I can also adjust it a little bit.

I want to make contributions like yours a bit more visible.

Skylion007 commented 3 years ago

Thanks for including me in the ReadMe. Left a minor nit on the PR, but otherwise looks good!

MartinThoma commented 3 years ago

Merged and released :-)