乾明 编译整理自 量子位 报道 | 公众号虽然好用但用好真的很难。尤其是函数部分只要写不好后面的一连串人都会遭殃。看又看不懂测试起来也麻烦维护又维护不动真是让人头疼。那怎么写好一个函数呢《 》一书的作者在上发表了一篇文章给出了6个建议。希望能够给你带来帮助。什么样的函数是一个好函数有着“好”之称的函数与有着“差”之名的函数之间存在着怎样的不一样呢, 每个人对于此都持有自身单独的理解, 依据我个人的理解, 要是一个函数能够契合下面诸多条件当中的大部分, 我就会判定它是一个标明为“好”的函数。合理命名, 单一功能, 伴有文档字符串来进行说明, 返回一个值, 代码行数不超过50行, 是幂等函数, 或者是纯函数。对许多人来说这些要求可能显得过于苛刻了。然而, 我做出保证, 要是你的函数依照这些规则操作, 你的代码将会极其美观, 会致使其他程序员都羡慕到“眼红”的。接下来, 我会逐个对这些规则展开讨论, 随后归纳它们究竟是以怎样的方式缔造出“好”函数的。命名在这个问题上我最喜欢的一句话是计算机科学中只有两件事很让人头疼缓存失效和命名。就算这听上去特别莫名其妙, 然而给一件事情取名字实在是太难了, 下面呈现的是一个反面的例子。def get knn():原文中这个代码没有放上去量子位根据上下文信息进行了补充。这个函数命名的第一个问题是它使用了缩写。针对那些并非声名远扬的缩略语而言, 运用完整的英文单词会更为优良。缩写单词的唯一缘由仅仅是为了节省输入时间, 然而每一个现代编辑器具备自动填充功能, 因而你仅需输入一次全名便得以达成。缩写字一般是特定领域有的, 在上面的那段代码里, “KNN”说的是“K - ” , “df”说的是“” , 这是一种数据结构 , 要是另一个对这些首字母缩写不熟悉的程序员去读代码 , 基本很难看懂。关于这个函数的名字还有另外两个小瑕疵那“get”这个词, 是些无关紧要的存在。对于多数命名较好的函数来讲, 很显然有某些东东会从函数那儿返回, 其名会将这点给反映出来。这也并非是必要的。假若没有明确的参数名称, 函数的文档字符串或者类型注释会对参数的类型予以描述。那么我们如何重命名这个函数呢很简单def ():就算是并非专业领域内的人, 这个函数所要进行计算的具体内容也是明白清晰的, 参数具有一种能够非常明确地昭示其类型的名称。单一功能单一功能原则不仅适用于类和模块也同样适用于函数。一个函数应该只有一个功能。也就是说它应该只做一件事。一个关键的缘由在于, 若是每个函数仅仅去做一件事情, 唯有在这件事情出现变化时, 才需要对这个函数予以改变。此外, 要是这个函数的单独一项功能不再被需要了, 那就直接将它删除便可以了。还是用例子来说明吧。下面这个函数可以做不止一件“事情”:这个函数干了两件事情, 其一, 对那一组跟数字列表有关联的统计数据, 进行计算, 这得算一件事, 其二, 还要把它们给打印往, 这又是一件事, 是不是。若必须开展有关新的统计数据或者不同统计数据的计算, 又或者要对输出格式予以改变, 那么这个函数就得进行调整。所以, 这个函数, 最好写成两个独立的函数, 一个, 用来执行, 并且返回计算结果, 另一个, 用来获取这些结果, 并且打印出来。按这样的处理方法, 不但会使得测试函数更便利, 而且还准许这两个部分具备了可迁移性, 要是恰当的话, 还能够一同运用到各异的模块里。在进行编程期间, 你能够发觉存在好多数量的函数, 它们具备做好多好多不同事情的能力。同样的情况, 为了让代码具备更好的可读性以及可进行测试的特性, 这些函数是应当被分解成为更小的函数的, 并且其中每个函数仅有一个单独的功能。文档字符串虽说好像每个人都晓得PEP - 8, 它所界定的样式指南, 然而鲜有人清楚PEP - 257, 它是关乎文档字符串的。我于此处不会单纯地去重复PEP - 257的内容, 你能够在闲暇之际去读一下。当中的关键部分是:每个函数都得有个文档字符串, 用上恰当的语法以及标点符号, 用完整的句子去写, 首先要对函数的作用进行一句话的归总, 运用说明性语言并非描述性语言。在进行函数编写之际, 需培育起撰写文档字符串的习性, 并且于着手编写函数代码之前试着去写上一写。若你没办法去撰写一份明晰的文档字符串用于描绘函数所做之事, 这不就意味着需要再度思索下为啥要编写此函数了。返回值一些独立的程序, 可被认作是函数, 它们以参数形式接受一些输入, 且返回一些结果。有没有参数是可行的, 但是放在内部的那种角度来讲, 返回值是绝对得有的。你是没有办法去创建一个个没有返回值的一个函数。要是函数不存在返回值的话, 它就会“强制性”地返回None。你能够去测试一下这段代码滴:3.7.0, 于2018年7月23日, 20时22分55秒。克拉昂九点儿一零版, 括号内为克拉昂九零二点儿零三九点儿二。在“帮助”方面, 空字符串、空字符串或者空字符串用于更多情况。定义一个名为add的函数, 函数接受两个参数a和b, 在函数内部打印a与b相加的结果。之后将add函数对1和2进行相加操作的返回值赋给变量b, 此时打印出3, 再打印变量b, 最后询问b是否是这么个情况。实际上, 你会发觉b的返回值是None, 即便你所编写的函数不存在返回语句, 它依旧会返回某些东西, 并且, 每个函数都应当返回一个有价值的值, 如此测试起来会更便利, 毕竟, 你所撰写的代码理应能够被测试。试着去想象一下, 对测试之上的add函而言那会是怎么样超乎寻常的艰难。依照这个特定的概念, 我们理应如此这般去编写代码:如果那一行代码, 对去除两端空白字符后的字符串进行转换为小写操作后, 再判断其是否以特定字符串结尾能够生效的话, 那是由于每一个字符串方法具体来说就是去除两端空白字符的方法、转换为小写的方法以及判断是否以特定字符串结尾的方法, 这些方法各自在被调用后都会返回一个字符串作为相应的函数调用成果。当给定函数没有返回值时有一些常见的原因“它所做的只是一些与I / O相关的事情比如将一个值保存到数据库中。我不能返回任何有用的东西。”我不同意。如果操作顺利完成函数可以返回True。“我们修改了其中一个参数将其用作参考参数。”在这儿存在两点是需要予以留意的, 其一, 要竭尽最大的努力去防止这种行为方式, 其二, 用到恰到好处的程度会让人颇感惊奇, 然而若运用得不好则会极具危险性。其次, 哪怕这么去做是行不通的, 纵然复制某一个参数的耗费太过庞 high, 你依旧能够返回到前一条的建议之上。“我需要返回多个值。单独返回一个值是没有意义的。”可以使用元组返回多个值。总是返回一个有用的值调用者总是可以自由地忽略它们。函数长度让你去读那种长达200行的一个函数, 还要说出它到底所要做的是什么事情, 你会是怎样的一种感受?函数的长短对于程序的可读性有着直接的影响, 进而对其可维护性产生作用。由此可知, 需要让你所编写的函数尽量精简。虽然50行只是一个具有随意性数字, 然而在我个人看法中, 它是具有合理性的。并且, 你所撰写的大部分函数都应当更简短些。倘若存在一个函数, 其遵循着单一功能原则, 那么极有可能它是颇为短小的。若该函数属于纯函数或者是幂等的此为下文所探讨的情况, 那么它同样有可能是短小的。那般, 要是函数过长, 该如何去做呢? 进行重构。这般做会使程序的结构产生改变, 然而却不会改变其行为。从一个包含较多代码行的函数里头, 提取出几行代码, 然后将它们转变为拥有自己名称的函数。这情形, 是让较长函数变得简短的速度最快的方式, 并且也是最为常见的情况。给所有这些新函数取了合适的名称, 加上你, 所以生成的代码读起来会更容易的。幂等和函数纯度函数若是幂等的, 无论被调用了好多回, 针对给定相同的那组参数, 它始终都会返回一样的值, 是这样进行返回值操作的。结果并不依存于非局部变量, 以及参数的可变性, 还有来自任何I / O流的数据。下面的这个函数, 也就是被称为()的函数, 它属于幂等函数:无论一个人调用多少次, 调用次数为(7)答案始终是10。下面是一个并非幂等的函数:这个函数所返回的值, 其取决于I / O, 这个I / O也就是用户输入进去的数字, 对于的每一次进行调用, 都会返回不一样的值。倘若它被进行两次调用, 那么用户能够在第一次的时候输入3, 在第二次的时候输入7, 然后分别经由此次调用, 使得其返回6以及10这种情况得以实现。在现实当中, 幂等性存在着这样一个例子, 那便是于电梯前点击“向上”这一按钮。当首次进行点击操作之时, 电梯会接收到“你要上去”这样的“通知”。鉴于按按钮具备幂等性, 所以哪怕是反复去按这个按钮, 都不会产生什么实质性的影响。最终所呈现的结果是一模一样的, 不会有任何差异之处。为什么幂等很重要可维护性, 也就是那种具备可维护性质的可维护性。幂等函数极易进行测试, 原因在于当使用同样的参数之时, 它们始终会返回同一个结果。测试主要是查验经由不同调用而返回值的预期数值, 更为关键的是, 这些测试具备速度快的特性, 这属于单元测试里一个意义关键且时常被忽略的情况。当处理幂等函数之际, 重构属于简而易行之事。不管怎样于函数外部更改代码, 运用相同参数去调用它, 其结果始终皆那般。什么是纯函数在函数编程里头, 要是有个函数, 它是幂等的, 并且不存在能够观察到的副作用, 如此这般它就能堪称是纯函数。函数外部存有的任何东西, 针对此值而言均可不会产生影响。然而, 这并不表明函数不能够对诸如非局部变量或者I / O流之类的事物造成影响。举例来说, 要是上述()的幂等版本在将结果返回之前把该结果予以打印, 那么它依旧会被认定为是幂等的, 原因在于当它对I / O流实施访问操作时, 此种访问和从函数返回的那个值并无关联。进行print ( )的调用, 这仅仅是一种副作用, 除了返回相应的值之外, 还同程序的其他部分或者系统自身之间开展了一些交互。使我们将我们的那个示例朝着更前的方向推进一回。我们能够去编写如下的代码片段用以明确调用那个的次数:等于零定义函数, 函数体内容为双引号星号加三句号, 打印格式化字符串, 其中包含加号三, 赋值自增一, 加号三执行另一定义函数, 函数体内容为双引号单词加三个点句号结束, 双引号单词加星号加句号结束引号。此刻于我们而言, 正处于这样一种状况, 一方面是在朝着控制台进行打印操作, 这属于一种副作用, 另一方面呢是对一个并非局部的变量予以修改, 这是另一种副作用, 然而鉴于这两个方面的操作, 均不会对函数所返回的值产生影响, 所以该函数依旧具备幂等的特性。纯函数不存在副作用, 它不借助任何“外部数据”用以计算值, 除计算以及返回所述值之外, 它和系统或程序的其余部分不存在交互, 所以, 尽管我们新的()定义依旧是幂等的, 然而它不再是纯的。具有纯性质的函数, 不存在日志语句, 也没有print( )调用, 它们不会去使用数据库, 也不会联网连接, 它们不会对非局部变量进行访问或者修改, 它们不会调用任何其他并非纯性质的函数。简而言之, 在计算机科学环境里, 它们做不到爱因斯坦所说的“远距离幽灵般的行动”。它们不会对程序或系统的其余部分进行任何方式的修改。有这样些函数, 在以编写代码时所做的那种方式进行的命令式编程当中, 它们属于所有函数里最为安全的函数。它们可以被很容易地进行测试, 并且可以被维护。甚至比仅仅是幂等函数更加重要的是, 去测试它们基本上能够做到和执行它们一样快速。测试这件事情本身是极为简单的, 不存在数据库连接的情况, 也没有对其他外部资源进行模拟, 并且并不需要去安装代码, 除此之外, 后续也不存在任何需要进行清理这个动作的情况。具体一点来讲, 幂等性以及纯函数仅仅是一种期望情况, 并非是必需的。换而言之, 鉴于存在诸多好处, 我们或许会期望仅仅编写纯函数或者幂等函数然而这并不实际可行。重要的是, 我们得有意识, 着手去写代码, 用以隔离副作用, 以及外部依赖性, 而且这会让我们编写的每一行代码, 都更易于被测试。