我主要是在谈论 Python,但我认为这可能适用于大多数编程语言。如果我有一个可变对象,做原地操作并同时返回该对象是否是一个不好的主意?似乎大多数示例只是修改该对象并返回 None
,例如 list.sort
。
我主要是在谈论 Python,但我认为这可能适用于大多数编程语言。如果我有一个可变对象,做原地操作并同时返回该对象是否是一个不好的主意?似乎大多数示例只是修改该对象并返回 None
,例如 list.sort
。
是的,这是个不好的主意。原因是如果原地操作和非原地操作有着看似相同的输出结果,那么程序员就会频繁地混淆原地操作和非原地操作(例如 List.sort()
和 sorted()
),这将导致难以检测的错误。
返回自身的原地操作允许你执行 "方法串联",然而这是不好的实践,因为你可能会在一条链的中间不小心嵌入具有副作用的函数。
为了防止这样的错误,方法链应该只有一个具有副作用的方法,并且该函数应该处于链的末尾。链中之前的函数应该在没有副作用的情况下转换输入(例如,导航树、切片字符串等)。如果原地操作返回自己,则程序员很可能会无意中使用它来替代另一个返回副本且没有副作用的备选函数(再次提到 List.sort()
和 sorted()
),这可能会导致难以调试的错误。
这就是为什么 Python 标准库函数总是返回副本或返回 None
并在原地修改对象,但从不在原地修改对象并返回自身的原因。其他像 Django 这样的 Python 库也遵循这个做法(请参见有关 Django 的 这个非常相似的问题)。
.empty()
。例如2:当 API 如此常用以至于每个人都从一开始就知道它,并且没有返回副本的版本,比如 jQuery 的 .append()
。 - Samuel Rossillelist.sort
是原地排序,尽管名称听起来应该这样做。 - asmeurera.sort()
和 a[:2].sort()
将执行完全不同的操作(我猜如果使用像 numpy 的 array
这样使用视图的东西,则情况会有所不同)。也许重点是 sort
返回 None
保护您免受认为 a[:2].sort()
有任何用处的影响? - asmeurersort
用于其返回值时(因为它返回None
),它会立即失败,而不是悄悄地引起开发人员可能不想引起的副作用。 - Andrew Gorcesterself
将允许您对该对象执行method chaining,这是一种在同一对象上执行多个方法的便捷方式,它是面向对象编程中非常常见的习语。反过来,方法链接允许直接实现fluent interfaces。此外,它使一些函数式编程习惯更容易表达。StringBuilder
类允许在同一对象上多次调用append()
。在JavaScript中,JQuery广泛使用方法链接。Smalltalk将这个想法提升到了下一个级别:默认情况下,所有方法返回self
,除非另有说明(因此鼓励方法链接) - 与Python相比,它默认返回None
。self
是编程文化和惯例以及个人品味的问题。如上所述,有些编程语言鼓励这种做法(如Smalltalk),而有些则反对(如Python)。每种观点都有优点和缺点,并且存在激烈的讨论。如果你是一个严格遵守Python标准的程序员,最好不要返回self
- 只需知道有时打破这个规则可能会有用。这里关于就地操作不返回的答案让我有点混乱,直到我发现this other SO post链接到Python documentation(我以为我已经读过了,但可能只是略读了一下)。文档中提到就地运算符时说:
这些方法应尝试就地执行操作(修改 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不知道要重新绑定什么。通过保留对对象的单独引用,可以看到这种行为。
我想这取决于使用情况。我不认为从原地操作返回一个对象会有什么问题,除非你不使用结果,但如果你不是特别注重纯函数式编程,那这并不是真正的问题。我喜欢调用链模式,比如jQuery使用的那种,所以当函数返回它们所作用的对象时,我很感激,因为这样我可以进一步使用它。
list.sort
与sorted(list)
,list.reverse
与reversed(list)
)。 - Joel Cornett