【问题标题】:Any reason NOT to always use keyword arguments?有什么理由不总是使用关键字参数?
【发布时间】:2016-04-10 15:38:29
【问题描述】:

在进入 python 之前,我已经开始阅读一些 Objective-C / Cocoa 书籍。我记得,大多数函数都需要明确声明关键字参数。直到最近我都忘记了这一切,只是在 Python 中使用了位置参数。但最近,我遇到了一些由于位置不当而导致的错误——它们是鬼鬼祟祟的小东西。

让我开始思考——一般来说,除非有特别需要非关键字参数的情况——有什么好的理由不使用关键字参数吗?即使对于简单的功能,总是使用它们是否被认为是不好的风格?

我觉得我的大多数 50 行程序都定期扩展到 500 行或更多行,如果我习惯总是使用关键字参数,那么随着代码的增长,代码将更容易阅读和维护。有什么理由可能不是这样吗?

更新:

我得到的总体印象是,它是一种风格偏好,有很多很好的论据,它们通常不应该用于非常简单的论据,但在其他方面与良好的风格一致。不过,在接受之前,我只想澄清一下——这种方法是否会出现任何特定的非风格问题——例如,显着的性能损失?

【问题讨论】:

  • 我看到一个建议总是使用关键字参数的地方是__init__。见fuhm.net/super-harmful。这不像你在这里问的那么笼统,但它是一个支持你所建议的论据。
  • 我会说你问为什么 Python 会这样,因为他是从 Objective-C 转向 Python。如果你反其道而行之,你会问为什么 Objective-C 要求我们在任何地方都使用关键字参数! (但并不是说这是一个糟糕或不合理的选择;它只是一种不同的设计。但是,完整的语言会受到它的影响,因此我建议您不要尝试将这种设计复制到其他语言)跨度>

标签: coding-style python


【解决方案1】:

除了代码的清晰性和可读性之外,没有任何理由不使用关键字参数。是否使用关键字的选择应该根据关键字在阅读代码时是否添加额外的有用信息来决定。

我遵循以下一般规则:

  1. 如果难以从函数名称中推断出参数的函数(名称),请通过关键字传递(例如,我不想在我的代码中使用 text.splitlines(True))。
  2. 如果很难推断参数的顺序,例如,如果您有太多参数,或者当您有独立的可选参数时 – 通过关键字传递它(例如,funkyplot(x, y, None, None, None, None, None, None, 'red') 看起来不是特别好)。
  3. 如果参数的目的很明显,切勿通过关键字传递前几个参数。你看,sin(2*pi)sin(value=2*pi) 好,plot(x, y, z) 也是如此。

在大多数情况下,稳定的强制参数是位置参数,可选参数是关键字。

性能也可能存在差异,因为在每个实现中,关键字参数都会稍微慢一些,但考虑到这通常是一个过早的优化并且它的结果不会很重要,我认为这并不重要做出决定。

更新:非风格问题

关键字参数可以做位置参数可以做的所有事情,如果您要定义一个新的 API,除了可能的性能问题之外,没有任何技术缺点。但是,如果您将代码与现有元素结合起来,您可能会遇到一些小问题。

考虑以下几点:

  • 如果你让你的函数接受关键字参数,那将成为你接口的一部分。 您不能将您的函数替换为具有相似签名但相同参数的关键字不同的另一个函数。
  • 您可能希望在您的函数上使用装饰器或其他实用程序,假设您的函数采用位置参数。未绑定方法就是此类实用程序的一个示例,因为它们总是在将第一个参数作为位置参数读取后将其作为位置参数传递,因此即使定义中有参数 selfcls.method(self=cls_instance) 也不起作用。

如果您设计好 API 并记录关键字参数的使用,这些都不是真正的问题,尤其是如果您没有设计应该与已经存在的东西互换的东西。

【讨论】:

  • 完美答案。我要补充一点,Python 的方式类似于传递程序命令行选项的方式:必需的应该是一些和位置的,可选的应该作为选项传递(在某种意义上是关键字)。
  • 关键字参数真的会导致任何形式的显着性能损失吗?我想需要解析一两个额外的标记,但还有比这更深层次的原因吗?
  • @Rosh:严格来说,“关键字参数可以做位置参数可以做的一切”声明是不正确的。例如,元组解包不适用于关键字参数,而 def f((x, y)): … 是一个有效的定义。当然,它既不被广泛使用也不被推荐。
【解决方案2】:

如果您的考虑是提高函数调用的可读性,为什么不简单地将函数声明为正常,例如

def test(x, y):
    print "x:", x
    print "y:", y

并且只需通过显式声明名称来调用函数,如下所示:

test(y=4, x=1)

这显然会给你输出:

x: 1
y: 4

否则这个练习将毫无意义。

这避免了参数是可选的并且需要默认值(除非您希望它们是,在这种情况下,只需继续使用关键字参数!:) 并为您提供不受限制的命名参数的所有多功能性和改进的可读性按顺序。

【讨论】:

  • 完美回答乔治的问题。使 args 充当 kwargs。在Salty Crane Blog 上,他展示了如何在函数调用中混合 kwargs 和 args
  • 是的!这提供了帖子所要求的大部分好处,而没有更改函数参数规范会产生的所有缺点。
【解决方案3】:

好吧,我不这样做有几个原因。

如果您的所有参数都是关键字参数,它会增加代码中的噪音,并且可能会导致不清楚哪些参数是必需的,哪些是可选的。

另外,如果我必须使用你的代码,我可能想杀了你!! (开个玩笑),但每次都必须输入所有参数的名称......不是那么有趣。

【讨论】:

  • 我看过一些 Smalltalk 代码(基于 Obj-C 的)。当它做得好时,它读起来很自然。您看到的不是一个参数列表,而是一个句子。您必须以不同的方式来考虑如何命名参数,但具有 Objective-C 背景的人可能已经有了这种心态。
  • 我可以看到噪声参数。至于它们是否是可选的——你能举个例子吗?正如我所想的那样——无论参数是否是可选的,你都必须了解函数是如何工作的——在这种情况下,命名参数(如果命名得当)不会加快理解速度吗?跨度>
  • @Joe White 确实,这种编写参数的方式在 SmallTalk/Objective-C 中感觉很好,但这是因为这些语言被设计为一个整体来使用它,并且此类语言的开发人员需要花费很多时间关心以“正确的方式”做好它。请注意,例如,ObjC 参数是基于关键字和位置的,因此句子出现是强制的。问题是 Python 中的“正确方式”不同,遵循 Objective-C 的模式会导致奇怪的代码。
  • @brandizzi 公平点。如果应用程序由熟悉 Objective-C 的人编写和维护,它可以运行良好,但不会是 Pythonic。
【解决方案4】:

只是提供一个不同的论点,我认为在某些情况下命名参数可能会提高可读性。例如,想象一个在您的系统中创建用户的函数:

create_user("George", "Martin", "g.m@example.com", "payments@example.com", "1", "Radius Circle")

从该定义来看,这些值可能意味着什么并不清楚,即使它们都是必需的,但是对于命名参数,它总是很明显:

create_user(
    first_name="George",
    last_name="Martin",
    contact_email="g.m@example.com",
    billing_email="payments@example.com",
    street_number="1",
    street_name="Radius Circle")

【讨论】:

  • 这就是我喜欢学习 Cocoa/Obc 的原因。 - 第一次看到一个函数并不那么神秘 - 你通常可以很好地了解它的作用,我认为这很棒。我只是没有足够的经验知道大型项目代码是否足够简洁,以至于从长远来看不会对类型参数进行持续的权衡。
  • 我认为这取决于功能。对于def validate_email(email) 之类的情况,当您查看函数的实例时,它在做什么可能很明显。
  • 确实,关键字参数在将文字传递给函数时非常有用。但是,如果参数是名称足够清晰的变量,则会变得非常嘈杂。考虑:create_user(first_name=first_name, last_name=last_name, contact_email=contact_email, ...).
  • 这是一个很好的论点,当传递的参数的含义不清楚时,代码变得很难很快阅读。例如,将布尔值传递给函数通常是一个坏主意,例如:do_some_stuff(True)True 在这里真正完成了什么?从字面上看,这会损害代码的可读性,因为现在您必须查找所述函数的定义才能找到答案。 Python 的命名参数提供了一种很好的方式来提高代码的可读性,只要确保不要过度扩展它的使用,它有一个转折点,它实现了相反的效果。
【解决方案5】:

我记得在 UNIX 程序中读过一篇关于“选项”的很好的解释:“选项意味着是可选的,程序应该能够在没有任何选项的情况下运行”。

同样的原则可以应用于 Python 中的 keyword 参数。 这些类型的参数应该允许用户“自定义”函数调用,但是函数应该能够在没有任何隐式关键字-值参数对的情况下被调用。

【讨论】:

  • 那么......为什么 Python 3 添加了一种方法来获得必需的关键字参数?
  • def some_func(x, y, *, method, error='strict') -- methoderror只能通过关键字指定,method必须指定为没有默认值。
【解决方案6】:

有时候,事情应该很简单,因为它们很简单。

如果你总是强制你在每个函数调用中使用关键字参数,很快你的代码就会变得不可读。

【讨论】:

    【解决方案7】:

    当 Python 的内置 compile()__import__() 函数 gain keyword argument support 时,为了清晰起见,提出了相同的论点。似乎没有显着的性能影响(如果有的话)。

    现在,如果你让你的函数接受关键字参数(而不是在调用它们时使用关键字传递位置参数,这是允许的),那么是的,这会很烦人。

    【讨论】:

      【解决方案8】:

      当参数的含义很明显时,我看不到使用关键字参数的目的

      【讨论】:

        【解决方案9】:

        当您的参数列表很长且没有明确定义的顺序时(您无法轻松地想出一个清晰的方案来记住),关键字参数是很好的;然而,在很多情况下使用它们是多余的或使程序不太清晰。

        首先,有时记住关键字的顺序比记住关键字参数的名称要容易得多,并且指定参数的名称可能会使其不太清楚。使用以下文档字符串从scipy.random 中获取randint

        randint(low, high=None, size=None)    
        Return random integers x such that low <= x < high.
        If high is None, then 0 <= x < low.
        

        在我看来,当想要从 [0,10) 生成随机整数时,写 randint(10) 比写 randint(low=10) 更清楚。如果您需要在 [0,10) 中生成一个包含 100 个数字的数组,您可能可以记住参数顺序并编写 randint(0, 10, 100)。但是,您可能不记得变量名称(例如,第一个参数是否为 low、lower、start、min、minimum),一旦您必须查找参数名称,您最好不要使用它们(因为您只是查找正确的顺序)。

        还要考虑可变参数函数(具有可变数量参数且本身是匿名的)。例如,您可能想写如下内容:

        def square_sum(*params):
            sq_sum = 0
            for p in params:
                sq_sum += p*p
            return sq_sum
        

        可以应用一堆裸参数(square_sum(1,2,3,4,5) # gives 55)。当然,您可以编写函数来获取命名关键字可迭代 def square_sum(params): 并像 square_sum([1,2,3,4,5]) 一样调用它,但这可能不太直观,尤其是在参数名称或其内容没有潜在混淆的情况下。

        【讨论】:

          【解决方案10】:

          我可以看到的一个缺点是您必须为所有内容考虑一个合理的默认值,并且在许多情况下可能没有任何合理的默认值(包括None)。然后你会觉得有义务为逻辑上应该是位置 arg 的 kwarg 未指定的情况编写大量错误处理代码。

          想象一下每次都写这样的东西..

          def logarithm(x=None):
              if x is None:
                  raise TypeError("You can't do log(None), sorry!")
          

          【讨论】:

          • 请不要写if (x is None):之类的代码。这不是 C。我们不需要丑陋的括号。 if x is None:.
          • 我倾向于回避编辑其他人的帖子。也许我不应该这么多。
          • 当无效值会自动引发 ValueError 时,为什么需要检查 x 的值?在这种情况下,引发“BadArgsException”是非常多余的。
          • @cowbert ValueError 我不会“自动”提出。无论如何,关键是x 没有合理的默认值 - 有时位置参数更好。
          • 在您发布的示例中,签名是使用位置还是 kwarg 没有区别,因为使用位置时您可以轻松地将 x 分配给 None 并调用 logarithm(x) 并且您仍然需要处理NoneType。我不明白为什么人们会觉得“有义务编写大量错误处理代码”,使用默认值,因为只有 2 条代码路径:要么返回真实值,要么引发异常(或一些内部运算符/功能会做到这一点)。默认情况下你会引发一个异常,不需要额外的代码......
          【解决方案11】:

          我经常犯的一个错误是,在调用函数时,我忘记了必须在任何关键字参数之前指定位置参数。如果testing是一个函数,那么:

          testing(arg = 20, 56)
          

          给出SyntaxError 消息;类似:

          SyntaxError: non-keyword arg after keyword arg
          

          当然很容易修复,只是很烦人。因此,在您提到的几行程序的情况下,在为函数的参数提供漂亮的描述性名称之后,我可能只会使用位置参数。我不知道我提到的问题是否有那么大。

          【讨论】:

            猜你喜欢
            • 2012-01-18
            • 2020-10-05
            • 1970-01-01
            • 2023-03-26
            • 1970-01-01
            • 2023-03-13
            • 1970-01-01
            相关资源
            最近更新 更多