Python函数中参数名称应如何命名,以便接受单个对象或可迭代对象作为输入?

8
我在我的代码中有一些函数,可以接受一个对象或对象的可迭代输入。我被教导要为所有东西使用有意义的名称,但我不确定如何遵守这里的规则。应该如何称呼既可以是单个对象也可以是对象的可迭代参数?我想到了两个想法,但我都不喜欢:
1. `FooOrManyFoos` - 这表达了发生的事情,但我可以想象,对于不习惯它的人来说,可能很难立即理解它的含义。 2. `param` - 一些通用名称。这表明它可以是几种东西,但不说明参数的用途。
通常,我将对象的可迭代形式称为单个对象的复数形式。我知道这可能看起来有点强迫症,但 Python 应该是(在其他方面)易读性的。

2
你是否可以接受一个单一的对象,而不是一个包含一个对象的可迭代对象?这将使你的代码更加清晰易懂(例如,传递[foo]而不是foo)。 - Katriel
@katrielalex:这是可能的。我有C++背景,习惯使用重载来解决这种问题。也许我需要在这里重新思考一下。 - Björn Pollex
9个回答

7
我在我的代码中有一些函数,可以接受对象或对象的可迭代集合作为输入。这是一个非常特殊且通常很糟糕的做法。它是可以轻松避免的。例如,在调用此函数时传递 [foo] 而不是 foo。你唯一可以证明这样做的理由是当(1)您拥有期望单个对象或可迭代对象的软件安装基础,并且(2)您必须扩展以支持其他用例时。因此,只有在扩展具有现有代码库的现有函数时才能这样做。如果这是新开发,请不要这样做。
我想到了两个想法,但我都不喜欢: FooOrManyFoos - 这表达了其中的含义,但我可以想象有人不习惯它会立即理解它的含义。 什么?你是说你没有提供其他文档、培训、支持或建议吗?谁是“不习惯它”的人?与他们交流,不要对他们做出假设或想象。
此外,不要使用首字母大写的名称。 param - 一些通用名称。这表明它可以是几种东西,但并未解释参数的用途。
可怕。永远不要这样做。
我在Python库中查找了示例。大多数执行此操作的函数都具有简单的描述。

http://docs.python.org/library/functions.html#isinstance

isinstance(object, classinfo)

它被称为"classinfo",可以是一个类或类元组。

您也可以这样做。

必须考虑常见用例和异常情况。遵循80/20规则。

  1. 80%的情况下,您可以使用可迭代对象替换它而不会出现问题。

  2. 在剩余的20%情况下,您需要添加另一种情况,因为软件已经建立在假设之上(无论是可迭代还是单个项)。不要更改名称,只需更改文档。如果以前说“foo”,现在仍然说“foo”,但您使其接受“foo”的可迭代对象,而不对参数进行任何更改。如果以前说“foo_list”或“foo_iter”,现在仍然说“foo_list”或“foo_iter”,但它会静默地容忍单个对象而不会中断。

    • 80%的代码是遗留代码(“foo”或“foo_list”)

    • 20%的代码是新功能(“foo”可以是可迭代对象或“foo_list”可以是单个对象)


4

我想我来晚了,但我很惊讶没有人建议使用装饰器。

这与IT技术有关。
def withmany(f):
    def many(many_foos):
        for foo in many_foos:
            yield f(foo)
    f.many = many
    return f

@withmany
def process_foo(foo):
    return foo + 1


processed_foo = process_foo(foo)

for processed_foo in process_foo.many(foos):
    print processed_foo

我在Alex Martelli的帖子中看到了类似的模式,但我不记得链接了。


3

听起来你正在为像这样的代码丑陋而苦恼:

def ProcessWidget(widget_thing):
  # Infer if we have a singleton instance and make it a
  # length 1 list for consistency
  if isinstance(widget_thing, WidgetType):
    widget_thing = [widget_thing]

  for widget in widget_thing:
    #...

我的建议是避免过度使用接口来处理两种不同的情况。我倾向于编写更加重视方法复用和清晰命名的代码,而非使用参数的巧妙动态。

def ProcessOneWidget(widget):
  #...

def ProcessManyWidgets(widgets):
  for widget in widgets:
    ProcessOneWidget(widget)

通常,我会从这个简单的模式开始,但是当有机会获得效率优势并抵消额外代码复杂性和部分功能重复时,就可以优化“很多”情况。 如果这种约定看起来过于冗长,可以选择像“ProcessWidget”和“ProcessWidgets”这样的名称,尽管两者之间的区别仅为一个容易忽略的字符。


2

您可以使用*args魔法(可变参数)使您的参数始终可迭代。

func(arg1,arg2,...)这样将单个项目或多个已知项目作为普通函数参数传递,并在可迭代参数前加上星号,例如func(*args)

示例:

# magic *args function
def foo(*args):
    print args

# many ways to call it
foo(1)
foo(1, 2, 3)

args1 = (1, 2, 3)
args2 = [1, 2, 3]
args3 = iter((1, 2, 3))

foo(*args1)
foo(*args2)
foo(*args3)

1

你能用高层次的方式命名你的参数吗?读代码的人更想知道参数代表什么(“客户端”)而不是它们的类型是什么(“元组列表”);类型可以在函数文档字符串中定义,这是一件好事,因为它可能会在将来改变(类型有时是实现细节)。


我的问题在于像 clients 这样的名称暗示了多个对象。如果调用者只想传递一个对象,他可能会认为需要将其包装成某个可迭代对象(这是我的直觉)。我正在寻找一种表达单个对象也可以的方法。 - Björn Pollex
1
@Space_C0wb0y: 我建议在参数名称中省略参数类型的指示,并在文档字符串中提前包含这些信息。毕竟,即使你知道列表可能只包含一行,你仍然经常需要调用类似 file_lines 的列表。没有人会希望你将其命名为 file_line_or_file_lines。因此,我建议如果你使用一个简短且高层次、描述性的名称,你的代码将会非常易读。:) - Eric O. Lebigot

1

我会做一件事,

def myFunc(manyFoos):
    if not type(manyFoos) in (list,tuple):
        manyFoos = [manyFoos]
    #do stuff here

因此,您不再需要担心它的名称。

在函数中,您应该尝试实现1个操作,接受相同的参数类型并返回相同的类型。

不要填充函数,而是使用2个函数。


1

既然您不关心获取哪种可迭代对象,可以尝试使用iter()获取参数的迭代器。如果iter()引发TypeError异常,则该参数不可迭代,因此您可以创建一个包含一个元素的列表或元组,这是可迭代的,就像Bob's your uncle一样简单。

def doIt(foos):
    try:
        iter(foos)
    except TypeError:
        foos = [foos]
    for foo in foos:
        pass    # do something here

这种方法唯一的问题是如果foo是一个字符串。字符串是可迭代的,因此传入单个字符串而不是字符串列表将导致迭代字符串中的字符。如果这是一个问题,您可以为其添加if测试。此时,对于样板代码来说,它变得冗长了,因此我会将其拆分为自己的函数。
def iterfy(iterable):
    if isinstance(iterable, basestring):
        iterable = [iterable]
    try:
        iter(iterable)
    except TypeError:
        iterable = [iterable]
    return iterable

def doIt(foos):
    for foo in iterfy(foos):
        pass    # do something

与一些回答者不同,我喜欢这样做,因为它消除了调用方在使用您的API时可能出错的一件事情。"在生成内容时要保守,在接受内容时要开放。"

回答您最初的问题,即您应该如何命名参数,我仍然会选择“foos”,即使您只接受一个项目,因为您的意图是接受列表。如果它不可迭代,那就是技术上的错误,尽管您将为调用方纠正这个错误,因为处理单个项目可能是他们想要的。此外,如果调用方认为他们必须传入一个可迭代对象,即使只有一个项目,那当然也可以正常工作,并且需要非常少的语法,所以为什么要担心纠正他们的误解呢?


0
我会选择一个能够解释参数可以是实例或实例列表的名称。比如说 one_or_more_Foo_objects。我认为这比单调无味的 param 更好。

-1

我现在正在处理一个相当大的项目,我们正在传递地图并仅调用我们的参数map。地图内容取决于被调用的函数。这可能不是最好的情况,但我们在地图上重复使用了很多相同的代码,因此复制和粘贴更容易。

我认为,与其按其名称命名它,不如按其用途命名它。另外,请注意,您不能对不可迭代的对象使用in


3
由Python自身定义的map作为参数名称可能会令人感到困惑,因为人们期望map指的是Python中的map函数。 - Eric O. Lebigot
1
同意@EOL所说的。避免使用内置函数的名称作为参数名是一个好主意。 - Manoj Govindan
我不是指完全通过复制粘贴来重用代码。就像我说的那样,这可能不是最好的解决方案。我的第二段更多的是我的答案。 - Falmarri

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接