Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the proper level of indent for hanging indent with type hinting in python?

What is the proper syntax for a hanging indent for a method with multiple parameters and type hinting?

Align under first parameter

def get_library_book(self,
                     book_id: str,
                     library_id: str
                     )-> Book:

Indent one level beneath

def get_library_book(
    self, 
    book_id: str, 
    library_id: str
) -> Book:

PEP8 supports the Indent one level beneath case, but does not specify if Align under first parameter is allowed. It states:

When using a hanging indent the following should be considered; there should be no arguments on the first line and further indentation should be used to clearly distinguish itself as a continuation line.

like image 445
Nelly Avatar asked Sep 12 '16 19:09

Nelly


2 Answers

PEP8 has many good ideas in it, but I wouldn't rely on it to decide this kind of question about whitespace. When I studied PEP8's recommendations on whitespace, I found them to be inconsistent and even contradictory.

Instead, I would look at general principles that apply to nearly all programming languages, not just Python.

The column alignment shown in the first example has many disadvantages, and I don't use or allow it in any of my projects.

Some of the disadvantages:

  • If you change the function name so its length is different, you must realign all of the parameters.
  • When you do that realignment, your source control diffs are cluttered with unnecessary whitespace changes.
  • As the code is updated and maintained, it's likely that you'll miss some of the alignment when renaming variables, leading to misaligned code.
  • You get much longer line lengths.
  • The alignment doesn't work in a proportional font. (Yes, some developers prefer proportional fonts, and if you avoid column alignment, your code will be equally readable in monospaced or proportional fonts.)

It gets even worse if you use column alignment in more complex cases. Consider this example:

let mut rewrites = try_opt!(subexpr_list.iter()
                                        .rev()
                                        .map(|e| {
                                            rewrite_chain_expr(e,
                                                               total_span,
                                                               context,
                                                               max_width,
                                                               indent)
                                        })
                                        .collect::<Option<Vec<_>>>());

This is Rust code from the Servo browser, whose coding style mandates this kind of column alignment. While it isn't Python code, exactly the same principles apply in Python or nearly any language.

It should be apparent in this code sample how the use of column alignment leads to a bad situation. What if you needed to call another function, or had a longer variable name, inside that nested rewrite_chain_expr call? You're just about out of room unless you want very long lines.

Compare the above with either of these versions which use a purely indentation-based style like your second Python example:

let mut rewrites = try_opt!(
    subexpr_list
        .iter()
        .rev()
        .map( |e| {
            rewrite_chain_expr( e, total_span, context, max_width, indent )
        })
        .collect::<Option<Vec<_>>>()
);

Or, if the parameters to rewrite_chain_expr were longer or if you just wanted shorter lines:

let mut rewrites = try_opt!(
    subexpr_list
        .iter()
        .rev()
        .map( |e| {
            rewrite_chain_expr(
                e,
                total_span,
                context,
                max_width,
                indent
            )
        })
        .collect::<Option<Vec<_>>>()
);

In contrast to the column-aligned style, this pure indentation style has many advantages and no disadvantages at all.

like image 68
Michael Geary Avatar answered Nov 15 '22 15:11

Michael Geary


Read the previous line of PEP 8 more carefully, the part before "or using a hanging indent".

Continuation lines should align wrapped elements either vertically using Python's implicit line joining inside parentheses, brackets and braces, or using a hanging indent.

This is intended to cover the first "yes' example, and your first example above.

# Aligned with opening delimiter.
foo = long_function_name(var_one, var_two,
                         var_three, var_four)
like image 44
Terry Jan Reedy Avatar answered Nov 15 '22 14:11

Terry Jan Reedy