google / yapf

A formatter for Python files
Apache License 2.0
13.74k stars 887 forks source link

Knob to set blank lines near imports, classes, functions and methods #347

Open advance512 opened 7 years ago

advance512 commented 7 years ago

BLANK_LINE_BEFORE_NESTED_CLASS_OR_DEF allows adding a blank line before nested classes or methods.

Ideally, a knob would allow setting a specified amount of blank lines near:

Alternatively, offer a knob can allow "maximum blank lines", thus allowing blank lines after imports (for example). Right now they get formatted away.

bwendling commented 7 years ago

This is possible. I'll mark it as an "enhancement". Thanks!

jamesbeith commented 7 years ago

yapf and isort currently handle blank lines near Top-level imports and comments differently. isort is happy with this:

from foo import bar

# Public

def baz():
    pass

but yapf wants to remove a line between the import and the comment.

from foo import bar

# Public

def baz():
    pass

Irrespective of isort's behaviour, it would be nice to control the number of spaces here so there's 2 (ideally configurable) both above and below the comment. Should behave the same when there's a constant too.

from foo import bar

QUX = 'quux'

# Public

def baz():
    pass
advance512 commented 7 years ago

I have the same "infinite formatting loop" in PyCharm. In the JavaScript world, you have eslint --fix and you have WebStorm following your .eslintrc file - so there's no such issue.

This is one of the reasons I don't actually use yapf but rather a series of unify, autopep8, flake8 and pylint. I guess I'll add isort too. :)

bwendling commented 7 years ago

Adding the knobs shouldn't be too hard. I'll bump up the priority here.

dlopuch commented 6 years ago

I would also add a request knobs around number of blank lines inside functions (currently always exactly 1) and inside dictionaries (always exactly 0).

Kinda like eslint's no-multiple-empty-lines's max option.

Inside a function I'd like to have the ability to use 2 lines to break up different sections, and inside long dictionaries, I'd like to be able to group logical sets of key/values.

arareko commented 4 years ago

A knob BLANK_LINES_BETWEEN_TOP_LEVEL_IMPORTS_AND_VARIABLES will be available in version 0.31.0 of YAPF

diegovalenzuelaiturra commented 3 years ago

Hi, I'm not sure I've understood how to properly use the BLANK_LINES_BETWEEN_TOP_LEVEL_IMPORTS_AND_VARIABLES knob

Could you guide me with this ? - Here are my current configs files and an example code - With this settings, I think that isort would add a space after import pytest and yapf would remove it

( I think the docstring should probably be at the top to avoid this )

arareko commented 3 years ago

@diegovalenzuelaiturra Your isort config is missing lines_after_imports explicitly set to something, otherwise the default will be different depending on the code. According to the isort docs:

lines_after_imports: Forces a certain number of lines after the imports and before the first line of functional code. By default this is 2 lines if the first line of code is a class or function. Otherwise it's 1.

The number you set there should be the same you use for blank_lines_between_top_level_imports_and_variables (lowercase) in the YAPF config or there will always be a conflict otherwise.

There are a few special cases where the YAPF knob will not work as expected and will always conflict with what isort does, and it is when imports are immediately followed by either a comment, a docstring, function calls or conditionals. The knob does its job properly when a variable, a function definition or a class is what follows right after the imports.

Unfortunately, the problem is that it is very complex to implement the knob to work with comments, function calls or conditionals because they can be interspersed anywhere, even in between imports. And the way the YAPF tokenizer works would need to be refactored in such a way that it is able to look ahead by many lines in advance rather than just comparing in between "last", "current" and "next" lines (which is what it currently does).

A simple (but not so great) solution to let both tools coexist for those files which cause trouble is to disable YAPF for the scope of the imports section. For example, supposing you set the number of lines after imports to be 2 (two) for both isort and YAPF, this is how your script should look like:

# !/usr/bin/env python
# some comments
# yapf: disable
import os

import pytest

# yapf: enable
"""
Some docstring.
"""

def some_function():
    pass

I hope this helps.

camillesf commented 2 years ago

Hello!

Thank you @arareko for the great explanation, that is exactly the issue that I'm struggling with. The examples from @jamesbeith are very clear too.

the problem is that it is very complex to implement [...] A simple (but not so great) solution to let both tools coexist [...]

Four files in my codebase are affected, and I've been able to fix three of them with a single # yapf: disable either in the first line after imports (e.g. a try), or in the last import.

For the one case I couldn't fix that way, I thought of this (a dummy function or variable):

import os

import pytest

_ = 'google/yapf#347'

OTHER_VAR = object()
...

Or perhaps:

import os

import pytest

def _imports_end():
  """Play nice with isort, yapf#347"""

OTHER_VAR = object()
...

Aesthetically, I'd say I'll take a single # yapf: disable directive over either of the above, but—since it's in a single file for now—I think I prefer these, to # yapf: disable + # yapf: disable pairs (until #429 is implemented? Is that one less complex to implement, to what is remaining here? @arareko)

arareko commented 2 years ago

Aesthetically, I'd say I'll take a single # yapf: disable directive over either of the above, but—since it's in a single file for now—I think I prefer these, to # yapf: disable + # yapf: disable pairs (until #429 is implemented? Is that one less complex to implement, to what is remaining here? @arareko)

@camillesf I actually am not very aware of #429 (I'm not a YAPF developer but a very occasional contributor), and right now can't remember what the code looks like to actually know if it would be less complex to implement or not.

With regards to your examples, IMO they only make your code a little more cryptic and less easier to understand. The big advantage of the # yapf: directives is that they look like comments (to both the developer and the Python interpreter) and are very explicit about what their intention is.