【问题标题】:Is it a best practice to always use a concrete type (List, Tuple, etc.) for a return value instead of an abstract type (Sequence, Iterable)?始终使用具体类型(List、Tuple 等)而不是抽象类型(Sequence、Iterable)作为返回值是否是最佳实践?
【发布时间】:2021-10-09 15:44:50
【问题描述】:

我在某处读到,使用具体类型键入返回值是最佳实践,但尽管我付出了所有努力,但还是找不到我读到的资源... 至少有一个关于SO的答案。查看了 PEP 和 mypy 文档,但找不到...

【问题讨论】:

  • 不确定。对于任何给定的情况,我通常会尝试“尽可能具体”。
  • 根据经验,请使用在所有可能的返回值中都一致的最具体的类型。有些方法会根据参数类型返回不同的类型(例如,在给定列表时返回 list 的方法,在给定元组时返回 tuple),在这些情况下,它们必须将其返回类型列为包括两者。但提供更具体的返回类型始终是一个优势 - 毕竟,list 仍然符合Iterable 的条件,但反之则不然。
  • 我一直听到并遵循的规则是“返回时最具体,接受时尽可能广泛”。调用者应该被允许传入任何兼容的结构,同时也能够依赖给它们的类型。
  • 您应该尽可能具体地注释返回类型。换个角度想一想,如果您更喜欢特定类型而不是特定类型,那么使用 Any 注释成为最佳选择,但显然这没有用(对于您的函数的用户;对于作为开发人员的您来说,它没有用有所作为(除非您是自己函数的用户))。对于参数类型,它是不同的,您应该指出您期望从参数中获得的最低要求,以便在您的函数中工作(作为开发人员,您非常关心功能 的论点)。
  • @Josiah 我认为这真的取决于您是定义接口(协议等)还是具体方法。当然,对于接口,应用与函数参数相同的想法(即尽可能广泛),但对于具体方法,最好指出实际返回的类型(特定类型包括更抽象的属性) .

标签: python mypy typing


【解决方案1】:

您的类型注释应该表达您的意图承诺,仅此而已。

您认为是实现细节并保留更改权利的任何内容都应进行注释以维护该封装。参数应该被注释为您希望提交接受的最一般的东西。返回值应该被注释为您想要提交到您的返回值允许的最具体的事情。

假设您有一些代码进行强力分解,其中包括一个生成一些候选素数以循环遍历的类。最初,您将前 100 个候选人存储在一个列表中,并像这样公开它们:

def primes(self):
    return self._internal_primes

您可以将其注释为List 或Sequence,因为self._internal_primes 是这两者。但你不应该。

您不应该这样做的首要原因是最哲学的。此方法的预期目的是提供一些候选素数以进行循环。 Iterable[int] 是传达该目的的正确方式,无论幕后发生了什么。

第二个原因是封装和相关的灵活性。您不打算承诺将私有列表公开为您的实现。也许您预计将来将primes 重写为实现筛子的按需生成器。现在提交List 意味着你必须打破你的界面承诺才能这样做。

第三个原因是,即使您从未期望更改实现,您也可能不想宣传该实现的全部功能。例如,列表可以改变。如果你向某人保证这是一个列表,你也保证他们可以改变它。允许某人写p.primes()[2] = 72 并没有什么好处。通过暴露一个甚至没有变异方法的接口,这样的错误就不太可能漏掉而不被发现。

【讨论】:

  • __iter__ 必须返回 Iterator 而不是 Iterable。返回 Iterable 会导致类型错误。
  • 好点@Wombatz。我已经注意到编辑中的错误,等待正确修复的机会。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-02-14
  • 2020-06-17
  • 2019-05-11
  • 1970-01-01
  • 2013-02-21
  • 2017-10-06
相关资源
最近更新 更多