yapf icon indicating copy to clipboard operation
yapf copied to clipboard

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

Open advance512 opened this issue 8 years ago • 10 comments

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:

  • Top-level imports
  • Top-level classes and functions
  • Classes
  • Methods
  • Top-level comments

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

advance512 avatar Dec 13 '16 19:12 advance512

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

bwendling avatar Dec 21 '16 07:12 bwendling

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

jamesbeith avatar Feb 08 '17 10:02 jamesbeith

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. :)

advance512 avatar Feb 08 '17 17:02 advance512

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

bwendling avatar Feb 08 '17 17:02 bwendling

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.

dlopuch avatar Feb 02 '18 02:02 dlopuch

A knob BLANK_LINES_BETWEEN_TOP_LEVEL_IMPORTS_AND_VARIABLES will be available in version 0.31.0 of YAPF

arareko avatar Aug 20 '20 04:08 arareko

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 )

  • isort.cfg

    [settings]
    profile = google
    py_version = auto
    line_length = 120
    atomic = True
    from_first = True
    ensure_newline_before_comments = True
    
  • .style.yapf

    [style]
    based_on_style=google
    column_limit=120
    BLANK_LINES_BETWEEN_TOP_LEVEL_IMPORTS_AND_VARIABLES=1
    
  • example.py

    # !/usr/bin/env python
    # some comments
    import os
    
    import pytest
    """
    Some docstring.
    """
    
    
    def some_function():
        pass
    
    

diegovalenzuelaiturra avatar Jun 03 '21 18:06 diegovalenzuelaiturra

@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.

arareko avatar Jul 07 '21 20:07 arareko

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)

camillesf avatar Nov 18 '21 01:11 camillesf

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.

arareko avatar Dec 13 '21 21:12 arareko