译者|朱
修改|钱珊
本文将通过分析变量声明、模块所有权、依赖冲突、包管理、全局解释器锁以及并发和并行计算来解释为什么在开发大型项目时不推荐使用Python。
如果你是一个职业生涯很长的开发人员,那么你应该知道本节标题的真正含义:它是如何工作的?用户体验如何?什么是应用框架?数据是如何流动的?类似的问题还有更多。
我不会在这里为你回答所有这些问题。对于你想开始的任何项目来说,这些都是非常具体的问题。这些问题中的每一个都值得至少一篇文章来解释。
但是,我愿意回答一个问题:哪种语言最适合项目开发?
你可能认为这也是一个非常具体的问题,因项目而异。的确,你并没有完全错。但是每种编程语言都有一些缺陷。事实证明,Python也有很多陷阱。尤其是当你试图用它来构建一个大程序的时候。
ZenofPython在
相反,你不妨考虑下面的一小段C代码:
charnotpython[50]='这不是' tpython '
在我们回到Python之前,让我们仔细看看上面的C代码。
在这里,char是一个类型标识符,它告诉你它后面的一切都与字符串有关。"notpython & quot是我给这个字符串的名字。[50]供您参考,C将为此保留50个字符的内存空间。但是在这个例子中,我可以得到19个字符,——,每个字符对应一个空间位置,最后加上一个空字符\0。最后,用分号简洁地结束。
这种显式声明在C语言中是必要的。如果省略了显式声明,编译器会给出相应的错误提示!这种做事方式一开始看起来很傻很无聊,但是非常值得。
当你在两周或两年内阅读C代码时,比如你偶然发现一个你不知道的变量,你只需要检查它的语句。当然,如果你给它起一个有意义的名字,它会给你提供一个强有力的线索:它是什么,它在做什么,它需要在哪里,等等。
现在,我们用Python来比较一下。
在Python中,你几乎可以在编写代码的同时声明新的变量。但是,如果你不给它起一个有意义的名字或者至少留下一个注释,当你以后读这段代码的时候,你会发现问题一塌糊涂。
在Python中,除了深入分析源代码,你无法理解变量在做什么。
但是如果你在一个变量中犯了一个错误,你可能会毁掉你的整个代码,因为没有像c中那样的保护语句。
如果你在做一个更小的项目,比如几千行代码,这没关系;或者你的项目不是很复杂,会有一些问题。但是有了更大的项目,就麻烦大了。
是的,你可以在Python中进行显式变量声明。但事实上,只有最勤奋的程序员才能做到这一点。更实际的是,当编译器不& quot抱怨& quot,很多人会完全忘记这些多余的代码行。
写Python代码很快,对于小而简单的项目,读Python很容易。
但是,在阅读和维护大型Python项目时,比如查找描述性的变量名,注释所有代码,你最好是世界级的程序员,否则你就完了。
变量在哪里的问题& quot存在& quot不仅源于隐式声明。
变量也可能来自其他模块。它们通常采用my_module.my_variable()的形式。如果您对这样一个变量感到困惑,当您在主文件中检查它的其他位置时,您还没有完全解决问题。
您还需要检查是否有一行代码,像下面这样:
导入我的模块
from another _ moduleimportmy _ module
在第二行代码中,您告诉编译器您需要包含更多内容的模块中的哪个函数或变量。
这很烦人,因为你可以在https://pypi.org/,上找到更多的模块,你可以在你的电脑上导入任何其他的Python文件。因此,快速搜索函数或变量并不总是有效的。
但在某些情况下,问题可能会变得更糟。例如,一些模块可能依赖于其他模块。如果你运气不好,比如你导入了模块A、B、C,但是它们依赖于模块E、F、G、H,模块E、F、G、H依赖于I、J、k,突然你需要管理十个模块而不是三个!
更糟糕的是,有时候这种依赖并不是简单的依赖树。假设b和c也依赖于m和n,
J也依赖于M,C和H也依赖于Q……不必再强调,你应该明白了一切。这就像一个迷宫一样。Python程序员把此称为“依赖地狱”——的确是真实存在的。
而循环依赖算是迷宫中“最丑陋的野兽”。例如,如果模块A依赖于模块B,但模块B也使用模块A的一部分时——你的麻烦大了!
对于小项目来说,这没什么大不了的。但对于大的项目……你可能会一头扎进“原始丛林”。
我还没发泄完我对模块的咆哮:不仅是模块本身,还有它们的版本方面也存在问题。
原则上,Python拥有如此活跃的用户群,并且许多模块都定期更新,这是非常棒的。然而,只有一个问题:并非所有版本的模块都与其他模块兼容。
例如,假设你正在使用模块A和B。这两个模块都依赖于模块C。但是A在3.2或更高版本中需要C,而B在2.9或更低版本中需要C。你不在乎C,你只想要A和B。
世界上没有任何工具能帮你解决这场冲突。如果幸运的话,你会发现一个补丁,它是由与你遇到同样问题的人编写的。如果你没那么幸运,你就得自己写补丁了。
或者使用不同的软件包。或者完全重写其中一个包A或B,然后在需要错误版本的C的任何地方找到解决方法。
总之,在任何情况下,你都需要花费额外的时间。
这是一片“代码丛林”,你需要耐心和一些工具来驾驭它才行。
撇开依赖冲突不谈,还有一些不错的工具。例如,有一个名为pip的工具,可以使安装软件包变得容易。借助一个简单的文本文件“requirements.txt”,你可以指定要使用的软件包和版本,而不是弄乱文件头部。虚拟环境将所有软件包放在一个地方,并与主要的Python安装分开。
对于更大、更混乱的项目,还可以借助conda、YAML文件等途径来解决上述问题。
但无论如何,你都需要学习如何使用每种工具。而且,你需要花最少的时间来处理这些问题。
与上述“依赖地狱”世界联系在一起的是另一个令人不安的话题。
即使你已经解决了机器上的所有依赖性问题,并且Python像一匹新生的马一样平稳运行,也不能保证它会在其他人的机器上正常运行。
新生的马会跑吗?我不知道,但我似乎在努力让自己在生物学方面比以往任何时候都更有学识。闲言少叙,让我们回到Python话题。
像pip、文本文件requirements.txt和虚拟环境这样的工具将有助于你在一定程度上克服依赖地狱问题。但是,这种便利仅限于本地情形。
在每台新机器上,你都需要检查并可能重新安装每个需求及其版本。
唯一真正便携的解决方案是借助Jupyternotebooks。在这里你可以用任何你喜欢的版本写东西。在Jupyter中,所有内容都通过在线服务器运行,因此你可以将这些文件发送给任何人,他们的确能够“开箱即用”。
不过,这种方法也存在一个明显的缺点:Jupyternotebooks只有图形界面,而有时候仅仅使用图形界面,很难处理包含许多相互关联文件的大型项目。
也许这就是为什么我从未在Jupyternotebooks上看到过大型项目,尽管它们确实存在。
相比之下,其他一些计算机语言只使用虚拟机,这类问题即可迎刃而解。
假设你已经通过使用Jython或PyPy或类似的解决方案将项目移植到不同的机器上。所有这些操作都比虚拟机稍显笨拙。但是,至少它们能够正常工作了!
如果你正在组建一个大项目,你可能会集成C包、Fortran包等等。这样做有很多好处:Python中可能不存在C包,而且通常速度更快。出于历史原因,科学计算类软件包通常只存在于Fortran中。
实际上,您将不得不使用诸如gcc、gfortran之类的编译器,或许还有其他更多的编译器。
这太麻烦了!在Python代码中集成C模块的文档超过4500字——是本文的两倍!Fortran相关的文档也没有那么短。
用C语言构建整个项目最初可能会耗费更长时间;但是,你可以防止出现必须处理多个编译器和接口的情况。
C是如此古老的语言,以致几乎任何东西都有对应的C语言包装版本,甚至包括用户友好的机器学习软件包。
全局解释器锁(GIL)从Python诞生的第一天起就一直存在,它能够使终端用户的内存管理变得非常简单。
至少在较小的项目中,开发人员在使用Python时根本不需要考虑计算机内存问题。我们不妨将其与C语言比较一下:在C语言中,需要为每个变量保留一些内存。
基本上,GIL能够自动计算一个变量在代码的每个部分中被引用的次数。如果不再需要该变量,GIL就会释放它所占用的内存空间。
在小型项目中,GIL有助于提高性能,因为不必要的内存空间会被及时清除掉。
但在更大的项目中有一个问题:GIL不喜欢多线程!
当多个指令线程在同一进程资源上独立运行时,这是一种高性能执行程序的方法。机器学习模型很适合这样训练。
然而,只有一个小问题:GIL一次只能在一个线程上工作。
于是,会出现这样的情况:例如变量A在线程1上执行,而线程2已经用完了变量A,那么它的内存可能最终会被删除。这种情况取决于GIL当时在哪里运行。
因此,这可能会导致非常奇怪的错误,正如你所想象的……
这方面有一些变通办法,但都不是很漂亮。另一种选择方案是借助于多进程来完成计算。但在没有GIL的语言中,它通常不会像多线程那样快。
我们已经看到了并发的一个缺点。在进行多线程处理时,全局解释器锁会降低速度,或者导致奇怪的错误。
同样的缺点也存在于Python的协程中。
线程和协程之间有一些细微的区别,但最重要的一点是:协程一次执行一个任务,而线程可以同时执行多个任务,二者都是并发实现的。
当你有需要大量等待的任务时,协程非常有用,例如当你正在阅读网站数据并等待服务器响应时的情形。协程不会让计算机无所事事,而是给它分配另一项任务。
另一方面,当你有几个任务很耗时但不需要太多CPU消耗,也不需要太多等待时,线程是很有用的。流式数据就是一个例子。
如果你有一个CPU密集型任务,并且你想充分利用你的硬件,你可能想尝试一下使用并行计算。
多进程会成为你最好的朋友——它基本上是告诉计算机使用多个内核进行计算,从而节省大量时间。
不过,线程、协同程序和多进程这三种技术都面临类似的问题。它们在Python中实现起来并不难。但是代码看起来很笨重,很难阅读,尤其是对于初学者来说。
相比来说,Clojure、Go和Haskell等语言在并发性和并行性方面性能要好得多。但是,如果你不是在处理缓慢或密集的进程,那么你根本不必考虑这样的方案。但如果当你遇到这样的问题时,那么你可能需要考虑选择语言的问题了。
Python并不全是邪恶的,但它的确也存在缺点。
因此,如果你想要明确说明的变量和开发良好的包,而这些包不会让你轻易陷入依赖地狱,那么你可以选择C语言。此外,如果你想要任何机器都可以移植的东西,那么Java、Clojure或Scala都是不错的选择。因为它们在虚拟机上运行,所以你不会遇到与Python相同的问题。
还有,如果你想执行大而慢的任务,你可能想试试Go或Haskell。尽管一开始,它们比Python更难学习,但你投入的时间是有回报的。特别是,你总可以把多种语言组合起来使用。
Python非常适合快速编写脚本、初稿,甚至适合中等规模的项目。我认识的许多开发人员都用Python编写初稿和测试运行,然后再使用C、Go或Clojure重写重要的部分。这使代码执行更快,而且你仍然可以享受Python提供的优势。
在大型项目中,Python并不是被禁止使用,只不过这可能不是唯一使用的语言而已。你可以通过Python像胶水一样将C、Go或Clojure中的代码拼接在一起。此外,如果你已经达到了自己构建的阶段,那么请记住:没有任何一种语言是绝对优秀的!
总之,尽管Python有缺点,但它的确很酷,而且也很方便。通过使用Python集成其他语言中的代码,你总是可以绕过有关难点并最终解决问题。
译者介绍
朱先忠,51CTO社区编辑,51CTO专家博客、讲师,潍坊一所高校计算机教师,自由编程界老兵一枚。早期专注各种微软技术(编著成ASP.NETAJX、Cocos2d-X相关三本技术图书),近十多年投身于开源世界(熟悉流行全栈Web开发技术),了解基于OneNet/AliOS+Arduino/ESP32/树莓派等物联网开发技术Scala+Hadoop+Spark+Flink等大数据开发技术。
原文标题:Don’tusePython…ifyou’restartingabigproject,作者:AriJoury
链接:https://thenextweb.com/news/dont-use-python-for-big-projects
来源:51CTO技术栈
作者:朱先忠
上一篇:天冷就咳嗽(喉咙痒)是什么原因?
下一篇:塔防英雄传什么英雄好打