27

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

4

4 回答 4

33

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

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

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

这就是 Python 标准库函数总是返回副本或返回None并就地修改对象,但从不就地修改对象并返回自身的原因。其他 Python 库(如 Django)也遵循这种做法(参见这个关于 Django 的非常相似的问题)。

于 2012-11-26T22:29:09.390 回答
10

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

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

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

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

于 2012-11-26T22:20:40.100 回答
0

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

于 2012-10-25T05:59:34.853 回答
0

这里关于不从就地操作返回的答案让我有点困惑,直到我遇到另一个链接到 Python文档的 SO 帖子(我以为我读过,但一定只是略读)。参考就地运营商的文档说:

这些方法应该尝试就地执行操作(修改 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 不知道要重新绑定到什么。通过保持对对象的单独引用可以看到这种行为。

于 2020-04-24T00:50:10.157 回答