Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Formatter: Specify indentation for function parameters #8360

Open
LeonarddeR opened this issue Oct 30, 2023 · 27 comments
Open

Formatter: Specify indentation for function parameters #8360

LeonarddeR opened this issue Oct 30, 2023 · 27 comments
Labels
formatter Related to the formatter style How should formatted code look

Comments

@LeonarddeR
Copy link

The current indentation behavior for function parameters is: 4-space indentation at function definitions, like this:

def my_function(
    my_function_arg_1,
    my_function_arg_2,
    my_function_arg_3,
    my_function_arg_4,
):
    pass

This leads to reduced readability, because the function name and the argument names become harder to distinguish.

Adding one more level of indentation greatly improves readability:

def my_function(
        my_function_arg_1,
        my_function_arg_2,
        my_function_arg_3,
        my_function_arg_4,
):
    pass

PEP8 also has the same suggestion: https://peps.python.org/pep-0008/#indentation

Originally posted by @jsh9 in #7310 (comment)

@T-256
Copy link
Contributor

T-256 commented Oct 30, 2023

def my_function(
    my_function_arg_1,
    my_function_arg_2,
    my_function_arg_3,
    my_function_arg_4,
):
    pass

This leads to reduced readability, because the function name and the argument names become harder to distinguish.

I don't agree with this because it's rare in the wild to all parameters starts with fullname of function/class name.
BTW, in my view it's not reducing readability (this may differ for you).

@charliermarsh charliermarsh added formatter Related to the formatter style How should formatted code look labels Oct 30, 2023
@MichaReiser
Copy link
Member

I think this is the same as #8360

@MichaReiser MichaReiser closed this as not planned Won't fix, can't repro, duplicate, stale Oct 31, 2023
@dhruvmanila
Copy link
Member

I think this is the same as #8360

@MichaReiser Can you update the comment to link the correct issue? Currently, it's referring to itself ;)

@MichaReiser
Copy link
Member

Why do I always do this... 🤦 I'm trying to find the issue again but can't for some reason. I saw it before. I'll re-open this in the meantime.

@MichaReiser MichaReiser reopened this Oct 31, 2023
@rwightman
Copy link

I don't see why there is any need to downvote this, this a contentious issue based on numerous heated issues in black, etc that you can look up. The question is not whether you like it but whether there are enough who do.

The proposal is not to change the default, or force one way or another, but hopefully have a configuration option so that those of use who much prefer combining the PEP-8 recommendation of an extra indent level for function args with sad-face can do so.

Also, it is not just about readability in the contrived case where the args happen to match the function name, I feel the different alignment aids readbility regardless.

@jsh9
Copy link

jsh9 commented Oct 31, 2023

def my_function(
    my_function_arg_1,
    my_function_arg_2,
    my_function_arg_3,
    my_function_arg_4,
):
    pass

This leads to reduced readability, because the function name and the argument names become harder to distinguish.

I don't agree with this because it's rare in the wild to all parameters starts with fullname of function/class name. BTW, in my view it's not reducing readability (this may differ for you).

@T-256 you might be missing my main point there.

Even with different names, it's still not very readable:

def perform_some_tasks(
    data: List[float],
    arg2: Union[int, str, bool],
    hello: Optional[int],
    good: Dict[str, Any],
    morning: float,
) -> None:
    ...

Look at this instead (much more readable):

def perform_some_tasks(
        data: List[float],
        arg2: Union[int, str, bool],
        hello: Optional[int],
        good: Dict[str, Any],
        morning: float,
) -> None:
    ...

@LeonarddeR
Copy link
Author

@rwightman wrote:

The proposal is not to change the default, or force one way or another, but hopefully have a configuration option so that those of use who much prefer combining the PEP-8 recommendation of an extra indent level for function args with sad-face can do so.

Thanks for highlighting this. Indeed, it was not my intention to advocate for this to become the default in any way.

@jankatins
Copy link
Contributor

jankatins commented Oct 31, 2023

Just FYI: I downvoted this issue because I find any config for the format a negative thing, because it leads to longwinded bikesheding discussions and I really love the way black handled it: have some guiding principles (make diffs as short as possible, which means the indent should be as short as possible to not result in line breaks) and then take a decision and be done with it. The amount of time spent on discussing and/or tweaking styles (both on these issues/PRs or your own projects when you change some config) will (in my opinion) never be outweighed by any gains you have because some formatting is a tiny bit more readable than what black or now ruff does.

(and yes: been there, done it, will never get the time back I spent on making intelij format SQL the way I wanted it to... And also not the time I spend on resolving merge conflicts because I thought aligned AS was a good thing...)

@T-256
Copy link
Contributor

T-256 commented Oct 31, 2023

Even with different names, it's still not very readable:

Still, it's readable into my eyes, I think it's a personalization choose but perhaps modern editors with syntax highlighting solve your problem:

def perform_some_tasks(
    data: List[float],
    arg2: Union[int, str, bool],
    hello: Optional[int],
    good: Dict[str, Any],
    morning: float,
) -> None:
    ...

def perform_some_tasks(
        data: List[float],
        arg2: Union[int, str, bool],
        hello: Optional[int],
        good: Dict[str, Any],
        morning: float,
) -> None:
    ...

I disliked because I'd not like to encounter codebases with this option enabled, the reason is that going to show lines fat, and in syntax colorized editor it's uglier. (This is my taste, maybe it is different for youً!)

@josephsl
Copy link

Hi,

While indenting function arguments one extra level may not be readable for some, it may help programmers who must use alternatives to sight to program - several screen reading technologies include a feature to announce indentation level (one of them provides an option to announce indentation with audio). For example:

def my_function():
var1 = some_value

Versus:
def my_function(
var1 = some_value,
):

To most of us, the presence of comma at the end makes it clear that we are passing a keyword argument to this function. However, for people relying on assistive technologies (especially screen readers), programmers must infer from surrounding code (or subtle changes to output by text-to-speech engine as configured by the screen reading technology), unless they listen to speech output carefully, they may not know where function body starts and ends. A counterargument could be that blind programmers can ask the screen reader to announce all punctuation, and some would cite productivity as a reason to not take up that alternative. Another alternative is to ask people to use type hints in function definitions, but not all developers may heed that advice (type hints do inform programmers that we are reading function signature, not the body itself, quite useful if arguments are written on the same indentation as rest of the function body).

I understand that what I wrote above opens up a can of possibly unnecessary discussions, but I think it is also important to think about readability from the perspective of those who may not see what we wrote and must rely on alternative modes to figure out what's going on.

Thanks.

@rwightman
Copy link

Still, it's readable into my eyes, I think it's a personalization choose but perhaps modern editors with syntax highlighting solve your problem:

I use a 'modern editor', PyCharm, and it defaults to the extra indent level (continuation indent 8) for formatting because it's following PEP-8. Again, the extra indent is PEP-8. Sadface is not, but I do prefer sadface in combo with this.

@jsh9
Copy link

jsh9 commented Nov 1, 2023

Even with different names, it's still not very readable:

Still, it's readable into my eyes, I think it's a personalization choose but perhaps modern editors with syntax highlighting solve your problem:

def perform_some_tasks(
    data: List[float],
    arg2: Union[int, str, bool],
    hello: Optional[int],
    good: Dict[str, Any],
    morning: float,
) -> None:
    ...

def perform_some_tasks(
        data: List[float],
        arg2: Union[int, str, bool],
        hello: Optional[int],
        good: Dict[str, Any],
        morning: float,
) -> None:
    ...

I disliked because I'd not like to encounter codebases with this option enabled, the reason is that going to show lines fat, and in syntax colorized editor it's uglier. (This is my taste, maybe it is different for youً!)

I never rely on syntax highlighting because in many cases there is no syntax highlighting. Just to name a few:

  • Error logs (especially for web applications)
  • Stack traces (especially when running stuff in the terminal)
  • Code searches in terminal
  • Sharing code snippets with people in Slack
  • ...

@jsh9
Copy link

jsh9 commented Nov 1, 2023

The amount of time spent on discussing and/or tweaking styles (both on these issues/PRs or your own projects when you change some config) will never be outweighed by any gains you have because some formatting is a tiny bit more readable than what black or now ruff does.

@jankatins: I would suggest that we all refrain from making absolute statements like this (with phrases like "will never be outweighed by...").

If you can find statistics that support your "will never" claim, please feel free to share it here. (And note that the statistics need to be statistically rigorous.)

Otherwise, I would suggest not making design choices that hurt people's productivity.

By the way, "a tiny bit more readable" is your personal opinion, and there are a lot of people who do not agree with your opinion.

@jankatins
Copy link
Contributor

I would suggest that we all refrain from making absolute statements like this

Added a "In my opinion".

I'm looking forward to the statistics why this additional config improves readability and productivity and not decreases is because of the time lost doing bike shedding.

@Avasam
Copy link

Avasam commented Nov 7, 2023

I never rely on syntax highlighting because in many cases there is no syntax highlighting. Just to name a few:

  • Error logs (especially for web applications)
  • Stack traces (especially when running stuff in the terminal)
  • Code searches in terminal
  • Sharing code snippets with people in Slack
  • ...

Whilst I'm not a fan of this style, and I'd rather rely on syntax highlighting + empty left side, this is the best counter-argument I've seen to "just use syntax highlights". Validating it for readability reasons for those scenarios.

Not something I'd use in mine. But I could also work with a codebase that uses it (if autoformatted) w/o affecting my productivity.

@jsh9
Copy link

jsh9 commented Jan 15, 2024

Hi authors of Ruff, may I follow up on this issue? Do you have plans to include this configurability into Ruff in the near future? Thanks!

@MichaReiser
Copy link
Member

MichaReiser commented Jan 15, 2024

@jsh9 this is not on our near-time roadmap because there are other features with a wider reach that we want to work on.

@jsh9
Copy link

jsh9 commented Jan 15, 2024

Thanks @MichaReiser ! Would you be willing to point to us (people who would like this feature) where this feature is implemented in the code?

I'm not familiar with Rust, so it's difficult for me to contribute. But with your pointers maybe I (or other people) can take a stab at it.

@MichaReiser
Copy link
Member

The complexity of the requested feature isn't specifically implementing it, but:

  • Deciding if ruff wants to be less opinionated and support configuration flags as the one mentioned above
  • Decide on a formatting: I remember that there was some discussion around if "sad-face" should be on its own or the same line
  • Consider the style change holistically. What does it mean if we allow changing the indentation for function (and lambda?) parameters. Are there other places where it should be possible to customize the indentation? How does it fit into the larger formatting style?

The code change itself is probably rather straightforward (I say that often and am then surprised that formatter changes are always hard 😆 ). It might be as simple as adding an extra indent level to the code in

if self.parentheses == ParametersParentheses::Never {
write!(f, [group(&format_inner), dangling_comments(dangling)])
} else if num_parameters == 0 {
let mut f = WithNodeLevel::new(NodeLevel::ParenthesizedExpression, f);
// No parameters, format any dangling comments between `()`
write!(f, [empty_parenthesized("(", dangling, ")")])
} else if num_parameters == 1 && posonlyargs.is_empty() && kwonlyargs.is_empty() {
// If we have a single argument, avoid the inner group, to ensure that we insert a
// trailing comma if the outer group breaks.
let mut f = WithNodeLevel::new(NodeLevel::ParenthesizedExpression, f);
write!(
f,
[
token("("),
dangling_open_parenthesis_comments(parenthesis_dangling),
soft_block_indent(&format_inner),
token(")")
]
)
} else {
// Intentionally avoid `parenthesized`, which groups the entire formatted contents.
// We want parameters to be grouped alongside return types, one level up, so we
// format them "inline" here.
let mut f = WithNodeLevel::new(NodeLevel::ParenthesizedExpression, f);
write!(
f,
[
token("("),
dangling_open_parenthesis_comments(parenthesis_dangling),
soft_block_indent(&group(&format_inner)),
token(")")
]
)
}

write!(
    f,
    [
        token("("),
        dangling_open_parenthesis_comments(parenthesis_dangling),
        soft_two_level_block_indent(&group(&format_inner)),
        token(")")
    ]
)

It would require a custom soft_block_indent builder that writes two indents instead of just one:

let snapshot = f.snapshot();

f.write_element(FormatElement::Tag(StartIndent));
f.write_element(FormatElement::Tag(StartIndent)); // <---- this

match self.mode {
    IndentMode::Soft => write!(f, [soft_line_break()])?,
    IndentMode::Block => write!(f, [hard_line_break()])?,
    IndentMode::SoftLineOrSpace | IndentMode::SoftSpace => {
        write!(f, [soft_line_break_or_space()])?;
    }
}

let is_empty = {
    let mut recording = f.start_recording();
    recording.write_fmt(Arguments::from(&self.content))?;
    recording.stop().is_empty()
};

if is_empty {
    f.restore_snapshot(snapshot);
    return Ok(());
}

f.write_element(FormatElement::Tag(EndIndent));
f.write_element(FormatElement::Tag(EndIndent)); // <---- and this

match self.mode {
    IndentMode::Soft => write!(f, [soft_line_break()]),
    IndentMode::Block => write!(f, [hard_line_break()]),
    IndentMode::SoftSpace => write!(f, [soft_line_break_or_space()]),
    IndentMode::SoftLineOrSpace => Ok(()),
}

@stolenzc
Copy link

stolenzc commented Apr 2, 2024

I am watching this issue more than 1 month, and I always wait for implement this function in ruff. our company have some code style rules, All other rules can be satisfied through configuration, only this one I can't. This style also follow pep8 style. I like ruff, but I can't directly using it in my work and life.

@coinflip112
Copy link

I'd very much appreciate this feature. I'll get bullied into using Pycharm's formatter otherwise 😅

@jsh9
Copy link

jsh9 commented Jul 5, 2024

Hi @MichaReiser ,

May I follow up on this topic here? Have you and/or the Ruff team discuss these questions that you outlined above?

The complexity of the requested feature isn't specifically implementing it, but:

  • Deciding if ruff wants to be less opinionated and support configuration flags as the one mentioned above
  • Decide on a formatting: I remember that there was some discussion around if "sad-face" should be on its own or the same line
  • Consider the style change holistically. What does it mean if we allow changing the indentation for function (and lambda?) parameters. Are there other places where it should be possible to customize the indentation? How does it fit into the larger formatting style?

Thank you!

@MichaReiser
Copy link
Member

No, I'm sorry. We haven't decided on this yet

@vvlutin

This comment was marked as spam.

@vvlutin
Copy link

vvlutin commented Jul 30, 2024

Brackets are sufficient for readability in both cases above. So if readability is the goal then the first option is newline character before closing bracket. And the second option is complication of indentation logic by double indentation levels specifically for arguments lists. If complicating logic considered pythonic option, then maybe pep8 should allow both cases explicitly.

@tonycoco
Copy link

Google's .pylintrc currently sets indent-after-paren=4. I'm writing some of our open-source solutions and would like to set up a Ruff config equivalent. Looking to see if there's been any progress on a config/flag for this behavior.

@RoelantStegmann
Copy link

Yes, also following this. Hope it will become adopted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
formatter Related to the formatter style How should formatted code look
Projects
None yet
Development

No branches or pull requests