定义具有太多参数的函数以遵守 PEP8标准

我已经定义了一个带有一长串参数的函数。定义中的字符总数在80以上,不遵守 PEP8。

def my_function(argument_one, argument_two, argument_three, argument_four, argument_five):

避免水平滚动的最佳方法是什么?

73705 次浏览
def my_function(argument_one, argument_two, argument_three,
argument_four, argument_five):

An example is given in PEP 8:

class Rectangle(Blob):


def __init__(self, width, height,
color='black', emphasis=None, highlight=0):

So that is the official answer. Personally I detest this approach, in which continuation lines have leading whitespace that doesn't correspond to any real indentation level. My approach would be:

class Rectangle(Blob):


def __init__(
self, width, height,
color='black', emphasis=None, highlight=0
):

. . . or just let the line run over 80 characters.

Personally I also used to come up with the same solution as @BrenBarn 's second style. I like its way to properly represent the indentation of function parameters AND its implementation, albeit that "unhappy face" is somewhat unusual to some other people.

Nowadays, PEP8 specifically gives an example for such case, so perhaps the mainstream is going to adapt that style:

    # Add 4 spaces (an extra level of indentation)
# to distinguish arguments from the rest.
def long_function_name(
var_one, var_two, var_three,
var_four):
print(var_one)

We can of course go one step further, by separating each parameter into its own line, so that any future addition/deletion of parameter would give a clean git diff:

    # Add 4 spaces (an extra level of indentation)
# to distinguish arguments from the rest.
def long_function_name(  # NOTE: There should be NO parameter here
var_one,
var_two,
var_three,
var_four,  # NOTE: You can still have a comma at the last line
):  # NOTE: Here it aligns with other parameters, but you could align it with the "def"
print(var_one)

For Python code that uses type annotations, I suggest this:

def some_func(
foo: str,
bar: str = 'default_string',
qux: Optional[str] = None,
qui: Optional[int] = None,
) -> List[str]:
"""
This is an example function.
"""
print(foo)
...

If you use yapf you can use these options in .style.yapf:

[style]
dedent_closing_brackets = true
split_arguments_when_comma_terminated = true

If you use black you don't need to do anything, it uses the above style out of the box, if you add a trailing comma behind the last parameter.

I personally like to line up the params one-per-line starting with the open parentheses and keeping that indent. flake8 seems happy with it too.

def guess_device_type(device_name: str,
username: str=app.config['KEY_TACACS_USER'],
password: str=app.config['KEY_TACACS_PASS'],
command: str='show version') -> str:
"""Get a device_type string for netmiko"""

I find myself this way to be quite interesting:

def my_function(
argument_one, argument_two, argument_three,
argument_four, argument_five
):
...

it allows code-folding to reveal the function signatures quite easily, for instance, consider the below snippet:

def my_function(
argument_one, argument_two, argument_three,
argument_four, argument_five
):
s1 = 1
s2 = 2
if s1 + s2:
s3 = 3




def my_other_function(argument_one, argument_two, argument_three):
s1 = 1
s2 = 2
if s1 + s2:
s3 = 3

This way allows to code-folding the whole file and seeing all functions/signatures at once, ie:

enter image description here

1. What I would recommend

Even though it makes the function more verbose, for more than one argument, I think this is best — the example below is from Python —:

def my_function(
argument_one,
argument_two,
argument_three,
argument_four,
argument_five,
):
...

2. Why?

  1. Having each argument on one line makes it very simple to use git diffs, since changing one variable will only show that change. If you have more than one argument on each line, it's gonna be more visually annoying later.
    • Notice that the trailing comma after the last argument makes it easier to add or remove an argument later, while also conforming to the PEP 8's Trailing Comma Convention, and yielding a better git diff later.
  2. One of the reasons I really dislike the "align the arguments with the opening parenthesis" paradigm is that it doesn't yield easily maintainable code.
    • Kevlin Henney explains it is a bad practice in his ITT 2016 - Seven Ineffective Coding Habits of Many Programmers lecture (around 17:08).
    • The major reason it is a bad practice is that if you change the name of the function (or if it is too long), you're going to have to re-edit the spacing on all the arguments' lines, which is not at all scalable, though it may be (sometimes) (subjectively) prettier.
    • The other reason, closely related to the one immediately above, is about metaprogramming. If the codebase becomes too big, you will eventually find yourself needing to program changes to the code file itself, which might become hell if the spacing on arguments is different for each function.
  3. Most editors, once the parentheses are open and you press enter, will open a new line with a tab and shove the closing parenthesis to the next line, untabbed. So, formatting the code this way is very quick and kind of standardized. For instance, this formatting is very common in JavaScript and Dart.
  4. Lastly, if you think having each argument is too unwieldy because your function might have a lot of arguments, I would say that you're compromising easy formatting of your code due to rare exceptions.
    • Don't legislate by exceptions.
    • If your function has a lot of arguments, you're probably doing something wrong. Break it into more (sub)functions and (sub)classes.

3. Anyway...

A good convention is better than a bad one, but it's much more important to enforce one than be unnecessarily picky about them.

Once you've decided to use a standard, share your decision with your colleagues and use an automated formatter — for example, Prettier is a popular choice for JavaScript in VS Code; and the Dart language has standardized one globally: dartfmt — to enforce it consistently, diminishing the need of manual editing.