我在这里主要谈论的是 Python,但我想这可能适用于大多数语言。如果我有一个可变对象,那么让就地操作也返回该对象是一个坏主意吗?似乎大多数示例只是修改对象并返回None
。例如,list.sort
。
4 回答
是的,这是个坏主意。原因是如果就地和非就地操作具有明显相同的输出,那么程序员会经常混淆就地操作和非就地操作(List.sort()
vs. sorted()
),从而导致难以检测错误。
返回自身的就地操作可以让您执行“方法链接”,但是,这是不好的做法,因为您可能会不小心将具有副作用的函数埋在链的中间。
为了防止这样的错误,方法链应该只有一个具有副作用的方法,并且该函数应该位于链的末尾。链中之前的函数应该在没有副作用的情况下转换输入(例如,导航树、切片字符串等)。如果就地操作返回自己,那么程序员一定会意外地使用它来代替返回副本的替代函数,因此没有副作用(再次,List.sort()
vs. sorted()
),这可能导致难以调试的错误。
这就是 Python 标准库函数总是返回副本或返回None
并就地修改对象,但从不就地修改对象并返回自身的原因。其他 Python 库(如 Django)也遵循这种做法(参见这个关于 Django 的非常相似的问题)。
从修改对象的方法中返回修改对象可能有一些好处,但在 Python 中不建议这样做。修改操作后返回self
将允许您在对象上执行方法链接,这是在同一对象上执行多个方法的便捷方式,这是面向对象编程中非常常见的习语。反过来,方法链接允许直接实现流畅的接口。此外,它还允许更容易地表达一些函数式编程习惯用法。
举几个例子:在 Python 中,Moka库使用方法链。在 Java 中,该类允许对同一个对象StringBuilder
进行多次调用。append()
在 JavaScript 中,JQuery 广泛使用方法链。Smalltalk 将这个想法提升到了一个新的水平:默认情况下,除非另有说明,否则所有方法都会返回(因此鼓励方法链接) - 与默认返回的 Python 进行对比。self
None
这个成语在 Python 中并不常见,因为 Python 遵守命令/查询分离原则,该原则指出“每个方法都应该是执行操作的命令,或者是向调用者返回数据的查询,但不是两个都”。
考虑到所有因素,最终返回是一个好主意还是坏主意,这取决于self
编程文化和惯例,以及个人品味。如上所述,一些编程语言鼓励这样做(如 Smalltalk),而另一些则不鼓励这样做(如 Python)。每种观点都有优点和缺点,可以进行激烈的讨论。如果你是一个循规蹈矩的 Python 主义者,最好不要返回self
- 请注意,有时打破此规则可能会很有用。
我想这取决于用例。我不明白为什么从就地操作返回一个对象会受到伤害,除了你可能不会使用结果,但如果你对纯功能主义不是超级挑剔的话,那并不是真正的问题。我喜欢调用链模式,比如 jQuery 使用,所以当函数返回它们所作用的对象时,我很感激它,以防我想进一步使用它。
这里关于不从就地操作返回的答案让我有点困惑,直到我遇到另一个链接到 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 不知道要重新绑定到什么。通过保持对对象的单独引用可以看到这种行为。