【问题标题】:Is making in-place operations return the object a bad idea?使就地操作返回对象是一个坏主意吗?
【发布时间】:2012-10-15 06:32:45
【问题描述】:

我在这里主要谈论的是 Python,但我想这可能适用于大多数语言。如果我有一个可变对象,那么让就地操作也返回该对象是个坏主意吗?似乎大多数示例只是修改对象并返回None。例如,list.sort。

【问题讨论】:

  • 我认为这一切都与一致性有关。 Python 非常一致地认为可变对象上的方法是就地操作。只要您对此保持一致,就地操作返回对象或对象引用不应该有问题。
  • 但为什么会这样呢?
  • 我不是 100% 确定,但大多数时候,不需要就地操作来返回对象。毕竟,您不是在创建需要分配的新对象。此外,每个就地操作都有类似物,以使您返回某些内容以对其进行进一步操作这一事实变得显而易见。 (例如list.sort 与sorted(list)、list.reverse 与reversed(list))

标签: python coding-style mutable mutability


【解决方案1】:

是的,这是个坏主意。原因是,如果就地和非就地操作具有明显相同的输出,那么程序员会经常混淆就地操作和非就地操作(List.sort() 与sorted()),结果在难以检测的错误中。

返回自身的就地操作可以让您执行“方法链接”,但是,这是一种不好的做法,因为您可能会不小心将具有副作用的函数埋在链的中间。

为了防止这样的错误,方法链应该只有一个具有副作用的方法,并且该函数应该位于链的末尾。链中在此之前的函数应该在没有副作用的情况下转换输入(例如,导航树、切片字符串等)。如果就地操作返回自己,那么程序员一定会意外地使用它来代替返回副本的替代函数,因此没有副作用(再次,List.sort() 与 sorted()),这可能会导致错误这很难调试。

这就是 Python 标准库函数总是返回一个副本或返回 None 并就地修改对象,但从不就地修改对象并返回自身的原因。 Django 等其他 Python 库也遵循这种做法(请参阅 this very similar question 关于 Django)。

【讨论】:

  • 一般情况下是同意的,但我认为在某些特定情况下也有例外,这种情况并不少见。 Eg1:当方法的语义显然是一个就地操作时,如 jQuery 的.empty()。 Eg2:当 API 如此常用以至于每个人从一开始就知道它并且没有返回的版本时,就像 jQuery 的 .append()
  • 仅仅因为方法名是现在时态动词,并不意味着人们会发现操作就地执行很明显。在学习 Python 之后,我花了很长时间才始终记得 list.sort 就地行动,尽管这个名字听起来应该这样做。
  • 用就地操作结束链是否仍然存在混淆? a.sort() 和 a[:2].sort() 将做完全不同的事情(我想如果你使用类似 numpy array 的东西,它会使用视图)。也许重点是 sort 返回 None 可以防止您认为 a[:2].sort() 有什么用处?
  • 是的,关键是sort在用于返回值时会立即失败(因为它返回None),而不是默默地导致开发人员可能不打算的副作用导致。
【解决方案2】:

从修改对象的方法返回修改对象有一些好处,但不建议在 Python 中使用。在修改操作后返回self 将允许您对对象执行method chaining,这是在同一个对象上执行多个方法的便捷方式,这是面向对象编程中非常常见的习语。反过来,方法链接允许直接实现fluent interfaces。此外,它还可以更轻松地表达一些函数式编程习语。

举几个例子:在 Python 中,Moka 库使用方法链。在 Java 中,StringBuilder 类允许对同一个对象进行多次 append() 调用。在 JavaScript 中,JQuery 广泛使用方法链。 Smalltalk 将这个想法提升到了一个新的水平:默认情况下,所有方法返回self,除非另有说明(因此鼓励方法链接) - 与 Python 进行对比,默认情况下返回 None。 p>

这个成语在 Python 中并不常见,因为 Python 遵守Command/Query Separation Principle,其中指出“每个方法都应该是执行操作的命令,或者是向调用者返回数据的查询,但是不是两者”。

考虑到所有因素,最后返回self 是个好主意还是坏主意,这取决于编程文化和惯例,以及个人品味。如上所述,一些编程语言鼓励这样做(如 Smalltalk),而另一些则不鼓励这样做(如 Python)。每种观点都有优点和缺点,可以进行激烈的讨论。如果您是一个循规蹈矩的 Python 专家,最好不要返回 self - 请注意,有时打破此规则可能会很有用。

【讨论】:

  • 感谢您的出色回答,尤其是指向命令/查询分离原则的链接,它帮助我为我最近一直在考虑的一些设计权衡贴上了标签。
【解决方案3】:

我想这取决于用例。我不明白为什么从就地操作返回一个对象会受到伤害,除了你可能不会使用结果,但如果你对纯功能主义不是超级挑剔的话,那并不是真正的问题。我喜欢调用链模式,比如 jQuery 使用,所以当函数返回它们所作用的对象时,我很感激,以防我想进一步使用它。

【讨论】:

    【解决方案4】:

    在我遇到链接到 Python documentation 的this other SO post 之前,这里关于不从就地操作返回的答案让我有些困惑(我以为我读过,但一定只是略读)。参考就地运营商的文档说:

    这些方法应该尝试就地执行操作(修改 self)并返回结果(可以是但不一定是 self)。

    当我尝试使用就地操作而不返回self 时,它变成了None。在这个例子中,它会说vars 需要一个带有__dict__ 的对象。查看self 的类型显示None。

    # Skipping type enforcement and such.
    from copy import copy
    import operator
    import imported_utility # example.
    class A:
        def __init__(self, a, b):
            self.a = a
            self.b = b
        def one(self, scaler):
            self *= scaler
            return imported_utility(vars(self))
        def two(self, scaler):
            tmp = self * scaler
            return imported_utility(vars(tmp))
        def three(self, scaler):
            return imported_utility(vars(self * scaler))
        # ... addition, subtraction, etc.; as below.
        def __mul__(self, other):
            tmp = copy(self)
            tmp._inplace_operation(other, operator.imul)
            return tmp
        def __imul__(self, other): # fails.
            self._inplace_operation(other, operator.imul)
        # Fails for __imul__.
        def _inplace_operation(self, other, op):
            self.a = op(self.a, other)
            self.b = op(self.b, other)
    

    * 有效(二和三),但 *=(一)直到 self 返回。

        def __imul__(self, other):
            return self._inplace_operation(other, operator.imul)
        def _inplace_operation(self, other, op):
            self.a = op(self.a, other)
            self.b = op(self.b, other)
            return self
    

    我不完全理解这种行为,但对引用帖子的后续评论说,没有返回 self,就地方法真正修改了该对象,但将其名称重新绑定到 None。除非返回 self,否则 Python 不知道要重新绑定到什么。通过保持对对象的单独引用可以看到这种行为。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-10-15
      • 1970-01-01
      • 1970-01-01
      • 2012-10-16
      相关资源
      最近更新 更多