卸下技术债务!数据科学家需要简洁的Python代码
全文共6616字,预计学习时长19分钟
来源:Pexels
数据科学团队倾向于向两个互相竞争的领域发展。
一方面,有一些数据工程师非常看重高度可靠,稳固的代码,这些代码承担的技术债务少。另一方面,有些数据科学家非常重视概念验证(e.g.设置)中想法和算法的快速原型设计。
虽然更成熟的数据科学功能使双方之间拥有卓有成效的工作伙伴关系,建立了完善的CI / CD管道,并明确定义了职责分工,但早期团队通常由经验不足的数据科学家主导。所以,代码质量受到损害,技术债务以胶合代码,流水线丛林,无效的实验代码路径和配置债务的形式呈指数级累积[1]。
你能想象没有xkcd的生活吗?
最近,我写了一篇关于为什么数据科学家的代码倾向于遭受平庸之苦的评论文章,在这篇文章中,我希望介绍一些方法供新手数据科学家编写更清晰的Python代码并更好地构建小型项目,以及阐明减少技术债务在不经意间给你和所在团队带来的负作用。
下面的内容既不详尽也不足够严谨,旨在以浅显的方式介绍深层次建立数据科学项目的方式。有些要点很明显,有些则有点隐晦。
以下是本文内容的快速概述:(1)样式准则,(2)文档,(3)类型检查,(4)项目文件夹结构,(5)代码版本控制,(6)模型版本控制,(7)环境,(8)Jupyter笔记本,(9)单元测试,(10)记录。
Python 代码样式指引——PEP 8和linting
可读性非常重要,PEP8就是为此而打造,提供了编写简洁python代码的惯例。
符合PEP8规范是Pythonic代码的最基本要求。它表明你已经了解了Python开发人员的最基本预期。表明你能够与其他开发人员更轻松地协同合作,最重要的是,你的代码将更具可读性和一致性,并且更加方便自己理解。
在这里复制和重新格式化PEP8样式指南属于无用功。因此,你可以根据自己的喜好浏览pep8.org,查看示例并了解在微观层面(与在宏观层面或系统级别上编写简洁代码相反)上编写简洁代码的意义。
PEP8中提供的示例包括设置命名约定,缩进,导入和行长的标准。
顺便说一句, PEP8是应使用成熟的IDE(如看来像高级Python IDE的PyCharm)而非Sublime这样的简单文本编辑器来编写代码的原因之一。适用于Python的重量级IDE通常会遵循PEP8样式指南,它会在违反PEP8原则或自动重新格式化代码库时发出警告。
以下是四个(尽管实际上还有许多种)命令行工具,通过对源代码执行静态分析,以保持其简洁和一致性:
1. PyLint-最受欢迎的linter。它能够检查源代码,并充当错误和质量检查器。它比PEP8具有更多的验证检查和选项。但是,根据默认设置,它的输出有点过于繁琐,输出量偏大。
2. Black-自动重新格式化Python代码。 Black将重新格式化整个文件的格式,并格式化字符串以使其添上双引号。
3. PyCodeStyle——官方的linter工具,用于根据PEP8 python的样式规范检查python代码。
4. Flake8——封装pyflakes,pycodestyle和mccabe,它会验证封装pep8的封装器,pyflakes和循环复杂性。
旁注1. linter不会告诉你变量的命名方式是否正确。这项遭新手开发人员嘲笑的技能仍是值得掌握的技能。
旁注2. 在安装这些软件包之前,最好先处于虚拟环境中。此点会在后文阐述。
来源:Pexels
记录项目— PEP257 and Sphynx
PEP8概述了Python的编码规范,PEP257则对文档字符串的高级结构,语义和约定进行了标准化:基本内容及表达方式。与PEP8一样,这些并不是硬性规定,但是你应该明智地遵循这些准则。
如果违背了这些规范,那么代码看起来可能不太美观。
那文档字符串又是什么呢?
docstring是一个字符串文字,作为模块,函数,类或方法定义中的第一条语句出现。这样的文档字符串赋予该对象__doc__特殊属性。与PEP8一样,我并不会复制并重新格式化整个PEP格式,你应该在其他时间浏览它,此处仅提供函数文档字符串的两个示例。
1. 函数添加的单行文档字符串示例
- defadd(a, b):
- """Sum twonumbers."""
- return a + b
2.复合函数的多行文档字符串示例:
- defcomplex(real=0.0, imag=0.0):
- """Form a complexnumber.
- Keyword arguments:
- real -- the real part (default 0.0)
- imag -- the imaginary part (default0.0)
- """
- if imag == 0.0 and real == 0.0:
- return complex_zero
- ...
Sphynx
毕竟,格式正确的文档字符串已经到位了,接下来,你要将其转换为美化的项目文档。Sphynx是一款分解Python文档的生成器,它可以输出html,pdf,unix帮助页面等等。
这是Sphynx入门的很好的教程。
简而言之,在某个目录(通常是docs目录)中初始化Sphynx并设置其配置后,使用reStructuredText(* .rst)文件,在调用make之后,这些文件将转换为首选的输出文档类型。
另外,可以直接从文档字符串创建对Python程序其他部分的引用,这些字符串在输出文档中显示为链接。
为了说明Sphynx文档生成器的典型输出,此处是一个利用Sphynx生成文档的Python项目列表,虽然并不完整,但也足够学习了。示例包括matplotlib, networkX, Flask和 pandas。
类型检查 — PEP484, PEP526, 和 mypy
由于Python是一种动态类型的语言,因此默认情况下不会进行静态类型检查,优点是灵活性和快速的开发进度,但是也有弊端,因为不会在运行系统(编译时)之前捕获简单的错误,而是在运行时捕获它们。通常,像Python这样的动态类型化语言比静态类型化语言需要更多的单元测试。这很繁琐。在诸如Scala或Java或C#之类的语言中,类型声明以代码形式出现,并且编译器检查变量是否合法通过类型传递。
最终,静态类型扮演安全网的角色,在某些情况下你应该明智地使用。好消息是Python实际上提供了所谓的类型提示。
这是一个函数注释类型提示的示例。这意味着name应为str类型,函数也应返回str。
- defgreeting(name: str) -> str:
- return 'Hello ' + name
这是变量注释类型提示的示例。这意味着变量i应该是整数。请注意,以下语法适用于Python 3.6及更高版本。
- i: int =5
但坏消息是,类型解释被Python解释器忽略,并且没有运行时的效果(PEP当前仍在进行中)。
但是,如果没有运行时效果,为什么还要使用类型提示呢?有两个原因。首先,代码的作用更明确,知道如何运行。其次,由于可以在Python中使用静态类型检查器,因此默认情况下它不会运行。主要的静态类型检查器是mypy。
旁注1:由于类型提示的可选性,可以将它们放在代码中的任何位置,某些位置或不放置。试图使其与放置位置保持一致。
旁注2:如前所述,PyCharm能很好地施行PEP8标准。但是Pycharm在类型提示的情况下也同样很有用。它会自动检查类型提示,并在违背预期时给予提示。默认情况下,在PyCharm中,这些设置为警告提示,但也可以将其设置为错误提示。
来源:Pexels
项目文件夹结构— cookiecutter
正如凌乱的办公桌映射着头脑混乱,凌乱的文件夹结构也是如此。
从一开始,许多项目就受益于经过深思熟虑的目录结构。不幸的是,开始项目的一种常见方法是创建一个基本项目目录,并将所有内容直接归于目录中——从数据到笔记本到模型生成再到输出,而不考虑由于简单项目变得越来越复杂的参数化管道产生的不良影响。
最终,会面临某种形式的技术债务,此后必须以时间和精力去偿还。真的完蛋了吗?只需从一开始有远见地正确构造项目,就能避免这些。
我们为此苦苦挣扎的一部分原因是因为创建项目文件夹结构很繁琐。我们想深入探索数据并建立机器学习模型。但是相对较小的精力投入可以节省很多工作。
精简的文件夹结构促进最佳实践,简化关注点分离,并使学习(或重新学习)旧代码更加愉悦。
幸运的是,由于开源者的辛勤工作,有一种刚出炉的现成解决方案,用于创建我们想要的文件夹结构:cookiecutter。
创建许多数据科学项目共有的简洁文件夹结构只是一个命令。观看下面的视频,了解如何设置cookiecutter项目结构。
cookiecutter-数据科学
由 ericmjalbertasciinema.org录制
请注意,cookiecutter功能非常强大,实际上除了生成简洁项目文件夹结构外,还有更多功能。有关更多信息,请查看优秀的cookiecutter文档,它详实地阐明了数据科学项目的基本原理。
代码版本控制— git
这一点广为人知,我将不作赘述。软件开发的现代世界已经远离2000年代以前阴暗的狂野西部。每个人和他的下属都应该使用某种版本控制其项目。简单的协作,高效的版本控制,回溯和代码备份,这就足够了。
学会使用git是第一步,而用好git则是另外一码事了。
提交代码:“提早提交并经常提交”是合理的建议。避免提交大量代码,而建议提交小的独立功能。提交时,编写描述性的提交消息以准确记录更改。
多用户最佳实践:这在很大程度上取决于具体情境。我与团队合作的方式是拥有一个永远不会直接推送的master分支(主分支),一个正在运行代码的运行版本但从未直接对其进行开发的develop分支(开发分支),然后是团队中各个成员进行编码的feature分支(功能分支),这些功能随后合并到开发分支。当develop分支准备发布时,它会合并到master分支。这种封装的工作方式使多个开发人员在不干扰主代码库的情况下即可轻松地处理特定功能,降低了合并冲突的可能性。有关更多信息,请查看此链接。
模型和数据的版本控制— dvc
来源:Pexels
模型和数据与代码不同,因此绝不应将其归入代码存储库中。它们具有独特的生命周期管理要求,并具有不同的操作约束。但是,适用于代码版本控制的同一原理同样适用于数据和模型版本控制。
有一个优秀的开源工具dvc,它基于git构建。其本质上是一个数据管道构建工具,在一定程度上有助于解决数据科学中的重现性危机。它可以有效地将你的数据和模型推送到服务器,无论是本地数据,AWS S3,GCS,Azure,SSH,HDFS还是HTTP。
dvc 有3个核心主题:
1.大文件的版本控制
2.内置可重现的轻质管道
3.基于git的版本控制和实验管理
旁注:版本控制整个数据集的另一种方法是存储重新创建这些数据集所需的元数据,并在该参考元数据的背面创建模型引用。
基于环境构建— virtualenv
如果不在常规练习手册中划分环境,那么平衡系统范围的库版本可能会花费一到两个下午。也许你正在处理一个项目,然后移至另一个更新了numpy和poof的项目!那么第一个项目的依赖关系中断。
想象一下另一种情况,项目是由另一名团队成员从git撤出的,该团队成员正在使用项目中某个库的其他版本。他们编写了一些代码,这些代码依赖于自己版本缺乏的新功能,然后将其归为主分支(明智的选择是永远不要直接归为主分支)。你的代码因此中断了。
通过使用虚拟环境避免这种情况。对于简单的Python项目,请使用virtualenv。如果有复杂的环境需求,请使用类似docker的工具。这是一个简单的virtualenv工作流程:
1.在创建新项目时运行mkvirtualenv
2. pip安装分析所需的软件包
3.运行pip Frozen>requirements.txt来固定用于重新创建分析的确切软件包版本
4.如果发现需要安装另一个软件包,请再次运行pip Frozen>requirements.txt并将更改提交给版本控制。
关于Notebooks的提醒 — jupytext
Jupyter Notebooks广泛应用于数据科学。它们围绕可读性高的编程范例构建,并充当强大的媒介,使快速原型制作和易开发性融合,中间代码段与输出和说明文字交织,生成精美的演示文稿,非常美观。
但是,尽管Jupyter Notebooks有各种优势,也带来了不少的麻烦。你的计算机存储了多少个未命名的7.ipynb文件?Jupyter Notebooks最大的缺点可能就是它们与版本控制的配合太低效了。
原因是因为它们属于一类编辑器——“所见即所得”,此编辑软件允许用户查看与最终结果非常相似的内容。这就意味着该文档正在提取元数据,而Jupyter Notebooks是通过将代码封装在大型JSON数据结构中来嵌入代码的,其中包含二进制数据,如保存为base-64编码blob的图像。
这里声明一下,Git可以处理Notebook,因为你可以将它们推送到存储库中,但在比较不同版本的Notebooks时,Git无法很好地进行处理,并且难以提供对其中代码的可靠分析。
如果在公司内的Notebooks中或是在公开的GitHub上四处寻找信息,则很可能会发现数据库凭据,敏感数据,“请勿运行此单元格”代码块以及许多其他不良做法。为了避免这种麻烦,可以在每次准备登入Notebooks时都清除输出。但这这意味着每次都必须手动重新运行代码以生成输出,如果有多个用户使用同一Notebooks,已清除的笔记本元数据甚至也会发生变化。
这儿有多种工具可供选择。最受欢迎的一种是jupytext。这是作者撰写的很棒的教程,介绍了如何使用它。你所要做的就是安装jupytext,它将提供一个整洁的笔记本内下拉列表,用于下载代码的markdown版本,并省略输出,然后忽略.gitignore中的所有. ipynb文件。
单元测试— unittest
对代码进行单元测试可能是确保独立代码块按预期工作的有效方法。它允许自动化测试过程,及早发现错误,使过程更加敏捷,并最终帮助你设计更好的系统。python中最常用的测试框架是“unitest”的内置电池和内置模块,该模块提供了丰富的工具来构建和运行测试。另一个测试工具是pytest。
关于如何进行单元测试的教程有很多,本文提供一些关键的技巧。就像异常处理可以最大程度地减少你要捕获潜在错误的功能一样,每个单元测试都应侧重于一小部分功能以证明其正常工作。每个单元测试应完全独立,并且可以单独运行。测试套件应在开发新功能之前或之后运行;实际上,在将代码推送到共享存储库之前,使用hook以自动运行所有测试是一个好主意,这种类型的测试通常是某些CI / CD管道的一部分,开源代码示例服务之一叫做travis。
尝试加快测试速度吧!对每个测试函数使用长的描述性名称。测试应位于源代码的单独目录中,有关更多信息,请参见文件夹结构部分。
这里是一份教程,这是一份测试风格指南
日志记录 — PEP282,
日志记录是超越POC领域系统的关键部分。日志记录可以跟踪程序执行过程中发生的情况并将该信息保存到磁盘。通过保存浏览路径记录,它极大程度地促进调试并识别错误。
日志记录通常用于两个目的。诊断日志记录了与应用程序操作相关的事件。而审计日志记录了用于MI报告的基本运行时分析。每个日志消息有几种类型:debug、info、warning、error和critical。
logging是用于日志记录的内置Python标准库。
来源:Pexels
结语
如果坚持阅读到了结尾,那非常值得鼓励。希望你能有所收获。现在赶快去编程实践吧。
留言点赞关注
我们一起分享AI学习与发展的干货
如转载,请后台留言,遵守转载规范