为什么函数总是返回相同的类型?

sel*_*elf 25 python function return-type

我在某处读到函数应该总是只返回一种类型,所以下面的代码被认为是坏代码:

def x(foo):
 if 'bar' in foo:
  return (foo, 'bar')
 return None
Run Code Online (Sandbox Code Playgroud)

我想更好的解决方案是

def x(foo):
 if 'bar' in foo:
  return (foo, 'bar')
 return ()
Run Code Online (Sandbox Code Playgroud)

返回一个None然后创建一个新的空元组不是更便宜的记忆吗?或者这个时间差太小而不能注意到即使在较大的项目中?

S.L*_*ott 37

函数为什么要返回一致类型的值?符合以下两条规则.

规则1 - 函数具有"类型" - 映射到输出的输入.它必须返回一致的结果类型,否则它不是函数.一团糟.

在数学上,我们说一些函数F是从域D到范围R的映射 F: D -> R.域和范围构成函数的"类型".输入类型和结果类型与函数的定义和名称或正文一样重要.

规则2 - 当您遇到"问题"或无法返回正确的结果时,请提出异常.

def x(foo):
    if 'bar' in foo:
        return (foo, 'bar')
     raise Exception( "oh, dear me." )
Run Code Online (Sandbox Code Playgroud)

可以违反上述规则,但长期可维护性和可理解性的成本是天文数字.

"返回一个没有,这不是更便宜的记忆吗?" 错误的问题.

关键是不要以清晰,可读,明显的代码为代价来优化内存.

  • 最后一句话+1.可维护性是99%的代码应该考虑的实际成本. (10认同)
  • 尽管如此,你还没有给出任何理由.函数应返回相同类型的原因包括:1)"任何其他函数都是乱七八糟",2)"它必须返回相同的类型以适合函数的数学定义." 第一个是纯粹的修辞,第二个是问题.这不是我不同意的.我很乐意遵从你的蟒蛇实力.但是,你能否给出一些人们应该遵守这种做法的实际理由?例如,打破这种做法肯定不会<i>始终</ i>使代码不可读. (6认同)
  • -1:当在这个问题的上下文中使用'function'一词时,可以合理地假设它意味着Python函数,而不是数学函数.Python函数不总是返回相同的类型(例如`copy.copy()`)是很正常的.你的规则1说一些Python函数不是真正的函数,这是一个语义混乱. (6认同)
  • 标准库中还有很多示例可以返回一种类型或返回"None".它们不再"自发地返回不可预测的类型",而是OP的功能.例如`os.getenv',`re.search`,`sys.callstats`.提出异常是一件好事,但这是一个设计选择,返回"无"通常是非常合理的. (3认同)
  • `copy.copy()` 仍然是一个函数。签名(域和范围)是固定的。对于给定的域值,范围是可预测的。是的,有很多组合,但这些组合是微不足道的可预测的。它是一个在数学意义上具有简单、可预测特征的函数。它不会自发地返回不可预测的类型。 (2认同)

unu*_*tbu 19

不太清楚函数必须总是返回有限类型的对象,或者返回None是错误的.例如,re.search可以返回一个_sre.SRE_Match对象或一个NoneType对象:

import re
match=re.search('a','a')

type(match)
# <type '_sre.SRE_Match'>

match=re.search('a','b')

type(match)
# <type 'NoneType'>
Run Code Online (Sandbox Code Playgroud)

以这种方式设计,您可以测试与成语的匹配

if match:
    # do xyz
Run Code Online (Sandbox Code Playgroud)

如果开发人员需要re.search来返回一个_sre.SRE_Match对象,那么这个习语必须改为

if match.group(1) is None:
    # do xyz
Run Code Online (Sandbox Code Playgroud)

要求re.search始终返回一个_sre.SRE_Match对象,就不会有任何重大收获.

所以我认为你如何设计这个功能必须取决于具体情况,特别是你打算如何使用这个功能.

还要注意,_sre.SRE_Match并且NoneType是对象的实例,因此在广义上,他们是同一类型的.所以"函数应该总是只返回一种类型"的规则是没有意义的.

话虽如此,返回所有共享相同属性的对象的函数有一个美丽的简单性.(鸭子打字,不是静态打字,是蟒蛇的方式!)它可以让你把功能链接在一起:foo(bar(baz)))并确切地知道你将在另一端收到的对象的类型.

这可以帮助您检查代码的正确性.通过要求函数仅返回特定有限类型的对象,检查的情况较少."foo总是返回一个整数,所以只要在整个地方都可以使用整数foo,我就是金色......"


Jef*_*ris 10

函数应返回的最佳实践因语言而异,甚至在不同的Python项目之间也存在很大差异.

对于Python,我同意这样的前提:如果你的函数通常返回一个iterable,返回None是不好的,因为迭代而不进行测试变得不可能.在这种情况下,只返回一个空的iterable,如果你使用Python的标准真值测试,它仍将测试False:

ret_val = x()
if ret_val:
     do_stuff(ret_val)
Run Code Online (Sandbox Code Playgroud)

并且仍允许您在不进行测试的情况下迭代它:

for child in x():
    do_other_stuff(child)
Run Code Online (Sandbox Code Playgroud)

对于可能返回单个值的函数,我认为返回None是完全可以接受的,只记录这可能发生在docstring中.


rba*_*dar 7

以下是我对所有这些的看法,我将尝试解释为什么我认为接受的答案大多是不正确的.

首先programming functions != mathematical functions.你可以获得最接近数学函数的是如果你进行函数式编程,但即使这样,也有很多例子说不然.

  • 功能不必输入
  • 函数不必具有输出
  • 函数不必将输入映射到输出(因为前两个要点)

编程方面的函数只是简单地被视为一个内存块,其中包含一个开始(函数的入口点),一个正文(空或其他)和退出点(一个或多个取决于实现),所有这些都在那里为了重用您编写的代码.即使你没有看到它的功能总是"返回"一些东西.这个东西实际上就是函数调用之后的下一个语句的地址.如果您使用汇编语言进行一些非常低级的编程,那么您将会看到它的所有荣耀(我敢于您加倍努力并像Linus Torvalds一样手动编写一些机器代码,他们经常在此期间提到这一点.他的研讨会和采访:D).此外,您还可以获取一些输入并吐出一些输出.这就是为什么

def foo():
  pass
Run Code Online (Sandbox Code Playgroud)

是一段完全正确的代码.

那么为什么返回多种类型会变坏呢?嗯......除非你滥用它,否则根本不存在.这当然是一个糟糕的编程技巧和/或不知道你正在使用的语言可以做什么的问题.

返回一个None然后创建一个新的空元组不是更便宜的记忆吗?或者这个时间差太小而不能注意到即使在较大的项目中?

据我所知 - 是的,返回一个NoneType物体的记忆要便宜得多.这是一个小实验(返回值是字节):

>> sys.getsizeof(None)
16
>> sys.getsizeof(())
48
Run Code Online (Sandbox Code Playgroud)

基于您用作返回值的对象类型(数字类型,列表,字典,元组等).Python以不同方式管理内存,包括最初保留的存储.

但是,您还必须考虑函数调用周围的代码以及它如何处理函数返回的任何内容.你检查一下NoneType吗?或者您只是检查返回的元组是否长度为0?返回值及其类型的传播(NoneType在您的情况下与空元组相比)实际上可能会更加繁琐,无法处理并在您的脸上爆炸.不要忘记 - 代码本身被加载到内存中,所以如果处理NoneType需要太多的代码(即使是很小的代码但是数量很大),最好留下空元组,这样也可以避免使用你的人的思想混乱功能并忘记它实际上返回2种类型的值.

说到返回多种类型的值,这是我同意接受的答案(但只是部分)的部分 - 返回单一类型使代码更容易维护.检查类型A然后检查A,B,C,......等要容易得多.

然而,Python是一种面向对象的语言,因此继承,抽象类等等,所有这些都是整个OOP恶作剧的一部分.它甚至可以实现即时生成类,我几个月前发现它并且被惊呆了(从未在C/C++中看到过那些东西).

旁注:您可以在这篇精彩的概述文章中阅读一些关于元类和动态类的内容,并附有大量示例.

事实上,如果没有所谓的多态函数,甚至不存在多种设计模式和技术.下面我给你两个非常受欢迎的主题(找不到一个更好的方法来总结一个术语):

  • 鸭子打字 - 通常是Python代表的动态打字语言的一部分
  • 工厂方法设计模式 - 基本上它是一个基于它接收的输入返回各种对象的函数.

最后,您的函数是返回一个还是多个类型完全基于您必须解决的问题.这种多态行为会被滥用吗?当然,就像其他一切一样.


Ech*_*ica 5

我个人认为函数返回元组或None是完全正常的.但是,函数最多应返回2种不同的类型,第二种函数应为None.例如,函数永远不应返回字符串和列表.