中企动力 > 头条 > python崩溃

网站性能检测评分

注:本网站页面html检测工具扫描网站中存在的基本问题,仅供参考。

python崩溃

手把手:一张图看清编程语言发展史,你也能用Python画出来! 流量视频课程

img

Diao

关注

大数据文摘作品

作者:Peter Gleeson

编译:周佳玉、丁慧、叶一、小鱼、钱天培

今天文摘菌要教大家制作一张编程语言的关系网络图。如果不知道什么是关系网络图,可以点击下方链接先来看一下最终成果:

http://programming-languages.herokuapp/#,

我们可以在这里看到从过去到现在的250多种编程语言之间的“设计影响”的关系,下面是该演示的截图:

接下来,就让我们一起来学做这个关系网络图吧!

在当今的超连接世界,网络在现代生活中无处不在。举个栗子,文摘菌的周末这样开启——通过北京的交通网络进城,然后去最喜欢的咖啡店的一家分店,并将笔记本连上他们的Wi-Fi。接下来,登录各种常用的社交网站。

众所周知,在过去几十年来最有影响力的公司中,有一部分是因为网络的力量而获得成功。

Facebook、Twitter、Instagram、LinkedIn以及一些其他的社交媒体平台都依赖社交网络的小世界特性。这使他们能有效地将用户彼此(以及和广告商)之间连接起来。

谷歌目前的成功主要归因于他们早期在搜索引擎市场上的主导地位——部分原因是他们有能力通过他们的Page Rank网络算法来返回相关的结果。

亚马逊的高效配送网络使他们能够在一些主要城市提供当天发货。

网络算法在人工智能和机器学习等领域也是非常重要的。神经网络领域的研究非常热门。计算机视觉中许多必不可少的特征检测算法,在很大程度上也是依赖于使用网络来对图像的不同部分进行建模。

网络模型也可以解释大量的科学现象,包括有量子力学、生化途径以及生态和社会经济系统等。

那么,鉴于它们不可否认的重要性,我们应该如何更好地理解网络及其属性呢?

网络的数学研究被称为“图论”,是数学中较易理解的分支之一。 本文会介绍简单的网络知识,即便你没有相关背景知识也能轻松学会。

此外,我们将使用Python 3.x和一款非常棒的开源软件Gephi,通过关系网络将过去和现在的一系列编程语言的网络可视化联系起来。

首先,究竟什么是网络呢?

其实上面文摘菌举的栗子已经给了一些线索。交通网络由目的和路径的连接组成。社交网络通过个人和个人之间的关系进行连接。Google的搜索引擎算法通过查看有哪些页面链接到其他页面,来评估不同网页的“顺序”。

更一般地说,网络是可以用节点和边描述的任何系统,或者通俗来讲,就是我们所说的“点和线”。

边连接节点(语言)的例子(该网络表示了编程语言相互影响的关系)

有些系统以这种方式建立网络比较容易。社交网络也许是最明显的例子。计算机文件系统则是另一种方式——文件夹和文件通过其“父”和“子”关系创建连接。

但是,网络的真正威力其实在于,许多系统都可以从网络的角度来建模,即使这起初并不明显。

代表网络

我们应该如何将点和线的图片转换成我们可以压缩的数字信号呢?

其中有一个解决方案是绘制一个邻接矩阵来表示我们的网络。

如果你不熟悉矩阵这个概念,这听起来可能有点吓人,但不要害怕。 把它们想象成可以一次执行许多计算的数字网格就好。下面是一个简单的例子:

在这个矩阵中,每个行和列的交集都是0或1,这取决于各个语言是否被链接。你也可以根据上面的插图观察到!

对于要解决的大多数问题而言,矩阵是以数学方式表示网络的好方法。然而从计算的角度来看,它有时可能会有点麻烦。

例如,即使节点数量相对较少(比如说有1000个),矩阵中的元素数目也会大得多(例如,1000^2 = 1,000,000)。

许多现实世界的系统会产生稀疏网络,在这些网络中,大多数节点只能连接其他所有节点中的一小部分。

如果我们将计算机内存中1000个节点的稀疏网络表示为邻接矩阵,那么我们将在RAM中存储1,000,000个字节的数据。大多数将会是零。这里有一个更为有效的方法可以解决这个问题。

这种方法是使用边列表来代替邻接矩阵。这些正是他们所说的,它们只是一个节点对相互链接的列表。

表示网络的另一种手段是邻接表,它列出了每个节点后面与它进行链接的节点。例如:

收集数据,建立连接

任何网络模型以及可视化的表现都取决于构建网络本身所用的数据质量好坏。除了确保数据是准确和完整的同时,我们也需要一种推断节点之间边的合理方法。

这是相当关键的一步,随后对网络进行的任何分析和推断都取决于“关联标准”的合理性。

例如,在社交网络分析中,你可能会根据人们是否在社交媒体上相互关联来创建人与人之间的联系。在分子生物学中,你可能会基于基因的共同表达建立连接。

通常,我们还可以给边分配权重,从而体现关系的“强度”。

例如,对于网上零售的情况,可以根据产品被同时购买的频率来计算权重。用高权重的边连接经常被同时购买的产品,用低权重的边连接偶尔被同时购买的产品。和偶尔被同时购买的产品相比,那些不会被同时购买的产品根本就不会被网络连接。

正如你想的那样,将节点彼此连接的方法有可能很复杂。

但是对于本教程,我们将使用更简单的方式连接编程语言。我们要依靠维基百科。

维基百科所取得的的成功证明了它的可靠性。文章写作的开源合作方法也应该保证一定程度的客观性。

而且,它的页面结构相对一致,使其成为试用网页抓取技术的便利场所。

另一个便利工具是覆盖面广泛的、有据可查的维基百科API,这使得信息检索更容易。接下来让我们一起开始吧。

第一步:安装Gephi

Gephi可在Linux、Mac和Windows的环境下进行安装。

对于这个项目,我使用了Lubuntu。如果你使用的是Ubuntu / Debian,那么你可以按照下面的步骤来启动和运行Gephi。如果不是,那么安装过程也不会差太多。

下载最新版本的Gephi到你的系统(在撰写本文时是v.0.9.1)。准备就绪后,你需要提取文件。

你可能需要检查你的Java JRE版本。Gephi需要最新版本。在我刚刚安装的Lubuntu上,我只安装了default-jre,下面的一切将建立在此基础上。

在你准备好进行安装之前还有一步。为了将图表导出到Web,你可以使用Gephi的Sigma.js插件。

从Gephi的菜单栏中选择“工具”选项,然后选择“插件”。

点击“可用插件”标签并选择“SigmaExporter”(我也安装了JSON导出器,因为它是另一个有用的插件)。

点击“安装”按钮,你将完成整个安装过程。安装结束后,你需要重新启动Gephi。

第二步:编写Python脚本

本教程将使用python 3.x以及一些模块来进行简化。使用pip模块安装程序,需运行一下命令。

现在,在一个新的目录中,创建一个名为script.py的文件,并在你最喜欢的代码编辑器/ IDE中打开它。以下是主要逻辑的大纲:

首先,你需要有一个编程语言的列表。

接下来,通过该列表并检索维基百科相关文章的HTML。

从中提取出每种语言所影响的编程语言列表。这是我们连接节点的粗略标准。

同时,我们可以抓取一些关于每种语言的元数据。

最后,将收集的所有数据写入一个.csv文件。

完整的脚本在这里:

(https://gist.github/anonymous/2a6c841fe04ebc6d55acc259b4ac4f72)。

导入模块

在script.py中,首先导入一些模块。

准备好后——从创建一个节点的列表开始。这是Wikipedia模块派上用场的地方。它使得访问维基百科API非常容易。

添加下面的代码:

保存并运行上面的脚本,将看到打印出“List of programming languages”维基百科文章中的所有链接。

另外,还需要手动检查自动收集的数据。快速浏览后我们可以发现,除了许多实际的编程语言之外,该脚本还提供了一些额外的链接。

如:可能会看到“List of markup languages”,“Comparison of programming languages”等。

虽然Gephi允许你移除不想包含的节点,但为了节省时间,还是让我们先进行一轮数据清洗。

这些代码定义了要从数据中移除的子字符串列表。运行该脚本时遍历数据,移除所有包含不需要的子字符串的元素。

在Python语言中,完成这些只需要一行代码!

其他辅助函数

现在我们可以开始从wikipedia抓取数据并建立一个边列表(并收集所有元数据)。为了更简便,让我们首先定义一些函数。

抓取HTML

第一个函数使用BeautifulSoup模块来获取每种语言的Wikipedia页面的HTML。

这个函数使用urllib.request模块来获取“https://en.wikipedia.org/wiki/”+“编程语言”页面的HTML。

然后传给BeautifulSoup,它将读取HTML并解析为一个可以用来搜索信息的对象。

接下来,使用find_all()方法抓取感兴趣的HTML元素。

下面,是每种编程语言文章顶部的汇总表。该如何识别呢?

最简单的方法是访问其中一个编程语言页面。在这里,可以简单地使用浏览器的开发工具来检查感兴趣的元素。汇总表有HTML标记和CSS类“infobox”和“vevent”,因此可以使用这些来标识HTML中的表格。

用参数指定它:

find_all()返回符合标准的所有元素列表。为了指定感兴趣的元素,需要添加索引[0]。如果函数执行成功,则返回table对象,否则,返回None。

在使用了自动数据收集程序的情况下,全面的异常处理是非常重要的。如果没有,那么在最好的情况下如果脚本崩溃了,数据抓取程序需要重新开始执行。

在最坏的情况下,你获得数据集将包含不一致性和错误,这将为你后续的工作买下隐患。

检索元数据

下一个函数使用table对象来查找一些元数据。下面给出在表格中搜索语言第一次出现的年份的代码。

这个简短的函数以table对象作为参数,并调用BeautifulSoup的get_text()函数生成一个字符串。

下一步是创建一个名为year的子字符串。该字符串存储了在“appear”这个词首次出现之后的30个字符。这个字符串应该包含语言第一次出现的年份。

为了仅提取年份,使用正则表达式(通过re模块)来匹配任何以1到3之间的数字开头、并紧邻三个数字的字符串。

如果执行成功,函数将返回一个整数的year。否则,我们会得到“Could not determine”。你可能还想进一步挖掘元数据,例如范例,设计者或打字规律。

收集链接

我们还需要一个函数–该函数读入给定语言的table对象,输出一个包含其他编程语言的列表。

仔细观察上面代码中嵌套部分,到底是怎么回事呢?

这个函数利用了table对象具有结构一致性的事实。表中的信息存储在行中(相关的HTML标签是)。其中一行包含文字“\ nInfluenced \ n”。函数的第一部分查找这是哪一行。

一旦找到这一行,就可以确定下一行包含了被当前行影响的每种编程语言的链接。使用find_all(“a”)便可查找这些链接 - 其中参数“a”对应于HTML标签。

对于每个链接j,将其[“title”]属性添加到名为out的列表。对[“title”]属性感兴趣的原因是因为它将完全匹配存储在节点中的语言名称。

例如,Java作为“Java(编程语言)”存储在节点中,因此需要在整个数据集中使用这个确切的名称。

如果执行成功,getLinks()将返回一组编程语言。该函数的其余部分进行了异常处理,以防程序在某一阶段出现问题。

收集数据

最后,在一切准备就绪后执行脚本,收集数据并将其存储在两个列表对象中。

现在编写一个循环,将先前定义的函数应用于nodes中的每个词条,并将输出存储在edgeList和meta中。

该函数使用节点中的每种语言,并尝试从维基百科页面检索汇总表。

然后,该函数将检索表中列出的与目标语言所关联的全部语言。

对于同时出现在节点列表中的每种语言,将一个元素以[“source,target”]的形式添加到edgeList。通过这种方式,建立一个边的列表传给Gephi。

出于调试的目的,打印添加到edgeList的每个元素——这样做仅仅为了确保一切都工作。如果想要更彻底地调试,也可以添加打印语句到except语句中。

接下来,获取语言的名称和年份,并将其添加到元列表中。

写进CSV文件

一旦循环运行,最后一步是将edgeList和meta的内容写入到CSV文件。通过使用前面导入的csv模块,完成上一步骤就容易多了。

完成了!保存脚本,并从终端运行:

$ python3 script.py

当构建边列表时,你可以...

是时候丢弃 Python 2.0,将 100 万行的代码迁移到 Python 3.0 了! 流量视频课程

img

仲海莲

关注

Python 2 vs Python 3,究竟谁是性能之王?前段时间,Hackermoon 上一位叫 Anthony Shaw 的作者为我们做了一些测试,最终得出结论,虽然 Python 2 在加密和启动时间测试过程中,比 Python 3 的速度更胜一筹,但整体而言,Python 3 更快。而这是否就意味着我们还是将项目代码迁移到 Python 3.0 的好?接下来,本文来自全球著名的桌面应用之一的 Dropbox 将分享他们要弃用 Python 2.0 的真实原因,以及如何将百万行的代码成功迁移至 Python 3。

Dropbox 是世界上流行的桌面应用之一,你可以安装在 Windows、macOS 和部分的 Linux 发行版上。但你可能不知道,这个应用大部分是用 Python 写的。实际上,Drew 给 Dropbox 写下的第一行代码就是用的 Windows 版 Python,用的是老牌的 pywin32 等库。

虽然我们靠着 Python 2 支撑了这么多年(我们用过的最新版本是 Python 2.7),但我们从 2015 年就开始向 Python 3 转换了。今天我们终于完成了转换,你现在再装 Dropbox 的话,那么它用的是 Dropbox 定制版本的 Python 3.5。本文将介绍这次史无前例的 Python 3 转换的计划、执行和发布过程。

为什么选择 Python 3?

Python 3 的接受度在 Python 社区一直是热门话题。现在虽然 Python 3 已经广为接受(http://py3readiness.org/),一些非常流行的项目如 Django 甚至完全放弃了 Python 2 的支持,但这个话题的热度依然存在。对于我们来说,影响我们决定进行转换的几个关键因素有:

引人入胜的新功能

Python 3 的创新十分迅速。除了一长列(http://whypy3/)正常的改进(如 str 和 bytes 的讨论),还有几个功能吸引了我们的眼球:

类型标注语法:我们的代码量非常大,所以类型标注对于开发的效率非常重要。在 Dropbox 我们很喜欢 MyPy(http://mypy-lang.org/),因此原生的类型标注支持对我们很有吸引力。并行函数语法:许多功能都极度依赖线程和消息传递,我们采用的是 Actor 模式,使用了 Future 模块。而 asyncio 项目及其 async/await 语法有时能避免回调函数,从而获得更干净的代码。过老的工具链

随着 Python 2 日久年深,最初适合部署的工具链也大部分过时了。由于这些因素,继续使用 Python 2 会带来一系列的维护负担:

过老的编译器和运行时使得我无法们升级一些重要更新。例如,我们在 Windows 和 Linux 上使用 Qt,而最新版本的 Qt 包含了 Chromium(通过 QtWebEngine 实现),因此需要更现代的编译器。我们与操作系统的集成越来越深,而无法使用新版本的工具链,导致使用新版 API 的成本增大。例如,理论上 Python 2 依然需要 Visual Studio 2008 (http://stevedower.id.au/blog/building-for-python-3-5/)。但这个版本微软已经不再支持了,也与 Windows 10 SDK 不兼容。

冻结和脚本

当初,我们依靠“冻结”脚本为我们支持的每个平台创建原生应用程序。但是,我们并没有直接使用原生的工具链,如 macOS 的 Xcode,而是将创建各个平台上的二进制文件的任务交给其他程序去做,Windows 下是 py2exe,macOS 下是 py2app,Linux 下是 bbfreeze。这个完全面向 Python 的构建系统收到了 distutils 的启发,因为我们的应用最初只不过是个 Python 包,所以只需要一个类似于 setup.py 的脚本来构建。

随着时间的流逝,我们的代码量越来越大。现在,我们的开发已经不仅仅使用 Python 开发了。实际上,我们的代码现在由 TypeScript/HTML、Rust 和Python 混合组成,某些平台上还用了 Objective-C 和 C++。为支持所有组件,setup.py 脚本(内部的名字为 build-all.py)越来越大,越来越难以管理。

导火索就是我们与各个操作系统集成的方式。首先,我们越来越多地引入高级的 OS 扩展,如 Smart Sync 的内核组件等,这些组件不能,通常也不会使用 Python 编写。其次,像微软和苹果等供应商对部署应用提出了新的需求,因此经常需要用到新的、更复杂的工具,这些工具经常是这些供应商独有的(比如代码签名等)。

例如在 macOS 上,10.10 版本引入了新的应用扩展以便与 Finder 进行集成,就是FinderSync(https://developer.apple/library/archive/documentation/General/Conceptual/ExtensibilityPG/Finder.html)。它并不只是个 API,而是个完整的应用程序包(.appex),有自己的生存中崛起规则(即它由 OS 启动),而且对于进程间通信的要求更严格。换句话说,使用 Xcode 就很容易集成这些扩展,但 py2app 根本不支持它们。

因此,我们面临着两个问题:

由于我们使用 Python 2,因此无法使用新的工具链,所以集成新的 API 的代价更高(比如使用Windows 10的Windows Runtime)。我们的冻结脚本使得部署原生代码的代价更高(例如在 macOS 上构建应用扩展)。当我们计划转换成 Python 3 时,我们面临着两个选择:一是改进冻结脚本中的依赖,以支持 Python 3(从而支持现代编译器)和平台相关的功能(如应用程序扩展),二是不再使用以 Python 为中心的构建系统,完全放弃冻结脚本。我们选择了后者。

关于 pyinstaller 的一点:我们认真地思考过在项目早期使用 pyinstaller,但当时它不支持 Python 3,而且更重要的是,它和其他冻结脚本有类似的限制。不管怎样,这个项目本身很不错,我们只是觉得不适合我们而已。

嵌入 Python

为了解决构建和部署的问题,我们决定使用新的架构,在原生应用中嵌入 Python 运行时。我们不再将构建过程交给冻结脚本处理,而是使用各个平台自己的工具链(比如 Windows 下使用 Visutal Studio)来构建各种入口点。进一步,我们将 Python 代码抽象到一个库中,从而为多种语言“混合”的方式提供更直接的支持。

这样我们就可以直接使用各个平台的 IDE 和工具链了(例如可以直接添加原生的构建目标,如 macOS 上的 FinderSync),同时保留使用 Python 编写大部分应用程序逻辑的能力。

我们最后采用了下面的结构:

原生入口点:这些与各个平台的应用程序模型兼容。其中包括应用程序扩展,如 Windows 下的 COM 组件和 macOS 下的应用程序扩展。共享库可以使用多种语言编写(包括 Python)。表面上,这个应用能够更接近平台的要求,而在各个库的背后,我们可以有更大的灵活性来选择自己喜欢的语言和工具。

这种架构能提高模块性,同时还带来一个关键的副作用:现在可以同时部署 Python 2 库和 Python 3 库了。联系到 Python 3 转换工作,我们的转换过程就需要两步:第一,给 Python 2 实现新的架构;第二,利用它将 Python 2 替换成 Python 3。

第一步:“解冻”

第一步就是停止使用冻结脚本。目前,bbfreeze 和 pywin32 都不支持 Python 3,所以我们别无选择。我们从 2016 年开始逐步进行这项改变。

首先,我们将配置 Python 运行时的工作抽象化,将 Python 的东西放到一个新的库中,名为 libdropbox_bootstrap。这个库会代替一些冻结脚本提供的功能。尽管我们不再需要这些脚本,但它们仍然提供了一些运行 Python 代码所需的最基本的东西:

打包代码以便在设备上执行

这样我们才能发布编译好的 Python 字节码,而不用发布 Python 源代码。由于以前的每个冻结脚本在各个平台上有各自的格式,我们利用这个机会引入了一种新的格式,用于在所有平台上打包代码使用:

所有 Python 模块的 Python 的字节码 .pyc 都放在单一的 zip 文档中(如 python-packages-35.zip)。原生扩展. pyd / .so 由于是平台相关的原生动态链接库,他们必须安装在特定的位置,保证应用程序能毫无障碍地加载。Windows 下,这些文件与入口点(即 Dropbox.exe)放在一起。打包通过优秀的 modulegraph(作者是 py2app 和 PyObjC 的作者 Ronald Oussoren)实现。隔离 Python 解释器

这样能阻止我们的应用程序在设备上运行其他的 Python 源代码。有意思的是,Python 3 使得这种嵌入变得容易得多了。例如,新的 Py_SetPath 函数(https://docs.python.org/3/c-api/init.html#c.Py_SetPath)能够让我们将代码隔离,不需要再像 Python 2 时代在冻结脚本中进行某种复杂的隔离操作了。为了在 Python 2 中支持这一功能,我们在定制版本的 Python 2 中向下移植了这一功能。

其次,我们使用了平台相关的入口点Dropbox.exe、Dropbox.app和dropboxd 来使用这个库。这些入口点都是用各自平台的“标准”工具编译的,即 Visual Studio、Xcode 和 make,没有使用 distutils。这样我们就可以去掉冻结脚本带来的大量修补工作了。例如,在 Windows 下,这一步大大简化,只需为 Dropbox.exe 配置 DEP/NX 即可,就能将应用程序装箱单和资源嵌入了。

关于 Windows 的一点说明:现在,继续使用 Visual Studio 2008 的代价已经非常高了。为了正确地转换,我们需要一个能同时支持 Python 2 和 Python 3 的版本,最终我们采用了 Visual Studio 2013。为支持它,我们进一步修改了定制版本的 Python 2,使之能正确在 Visual Studio 2013 下编译。这些修改的代价进一步证明,我们转换到 Python 3 的决定是正确的。

第二步:混合

成功地转换如此之大(包含大约 100 万行 Python 代码)、安装量如此之高(大约有几亿安装)的应用程序需要逐步进行。我们不能简单地在某次发布中“改变一个开关”来实现转换,特别是我们的发布过程要求每两个星期给所有用户发布一个新版本。因此,必须找到一种办法,将 Python 3 的部分转换发布给一小部分用户,以便检测并修改 Bug。

为达到这一点,我们决定实现用 Python 2 和 Python 3 同时编译 Dropbox。这要求做到以下两点:

能够同时发布 Python 2 和 Python 3 的“包”,包括字节码和扩展,两者必须能够并存。在转换过程中强制使用混合的 Python 2 / 3 语法。我们采用上一步引入的嵌入式设计来实现:将 Python 代码抽象到库和包中,就能很容易地引入另一个版本。这样入口点程序(即 Dropbox.exe)就可以在初始化的早期控制选择哪个 Python 版本了。

我们通过手动连接入口点程序到 libdropbox_bootstrap 来实现这一点。例如在 macOS 和 Linux 下,我们在 Python 版本确定之后使用 dlopen/dlsym 来加载。在 Windows 下,使用 LoadLibrary 和 GetProcAddress。

对 Python 解释器的选择必须在 Python 加载之前完成,因此为了使之更顺畅,我们实现了命令行参数 /py3 用于开发,和一个保存在硬盘上的永久设置,以便通过我们的功能切换系统Stormcrow(https://blogs.dropbox/tech/2017/03/introducing-stormcrow/)来控制。

有了这些,我们就能在启动 Dropbox 客户端时动态选择 Python 版本了。这样就可以在 CI 基础设施中设置额外的任务来针对 Python 3 运行单元测试和集成测试。我们还在提交队列中增加了自动检查,以防止提交会破坏 Python 3 支持的改动。

通过自动测试确保没问题之后,我们就开始将 Python 3 的改动推送给真正的用户。我们通过远程的功能开关来将新功能逐渐开放给用户。首先对 Dropbox 推送改动,这样我们就能找出并改正大部分主要的底层问题。然后将范围扩大到 Beta 用户,他们的 OS 版本问题更加芜杂。然后最终扩展到稳定版。7 个月之后,所有的 Dropbox 都已经在运行 Python 3 了。为了尽可能提高质量,我们要求所有与转换相关的 bug 必须进行深入调查并彻底修复,才能扩大推送的范围。

逐渐推送到 Beta 版

逐渐推送到稳定版

到了版本 52 时,这个过程终于完成了。我们可以完全从 Dropbox 的桌面客户端中删掉 Python 2 了。

写在最后

一篇文章很难完整概括我们将代码迁移至 Python 3.0 的完整过程,这其中还有许多可以讨论的东西。接下来,我们还会在以后的文章中讨论:

我们怎样在 Windows 和 macOS 上报告崩溃,并利用这些信息调试原生和 Python 代码;怎样维护 Python 2 和 Python 3 混合代码,用到了哪些工具?整个 Python 3 转换过程中最值得讨论的 Bug 和故事。敬请期待,也欢迎在下方留言分享你对迁移过程的看法。

原文:https://blogs.dropbox/tech/2018/09/how-we-rolled-out-one-of-the-largest-python-3-migrations-ever/作者:Max Bélanger和Damien DeVille译者:弯月,责编:屠敏

微信改版了,

想快速看到CSDN的热乎文章,

赶快把CSDN公众号设为星标吧,

打开公众号,点击“设为星标”就可以啦!

Python程序员最常犯的10个错误,你中招了吗? 互联网视频课程

img

Phedra

关注

大数据文摘作品

编译:什锦甜、Gao Ning、小鱼

Python简介

Python是一种具有动态语义的、面向对象的解释型高级编程语言。因其内置了高级数据结构,并支持动态类型和动态绑定,使用Python进行快速应用程序开发十分便利。同时作为一门脚本语言,它兼容部分现有的组件和服务。Python还支持模块和各种库的扩展,有助于实现模块化编程和提高代码复用率。

关于本文

刚接触这门语言的新手可能会对Python简洁灵活的语法有些不适应,或是低估了Python强大的性能。鉴于此,本文列出了Python开发人员常犯的10个小错误,资深程序猿也难免会中招哦。

本文供Python高级开发人员参考,Python小白可以参考下面这篇文章:

http://onlamp/pub/a/python/2004/02/05/learn_python.html

常见错误1:滥用表达式作为函数参数的默认值

Python允许开发者指定函数参数的默认值,这也是Python的一大特色,但当默认值可变时,可能会给开发者带来一些困扰。例如下面定义的函数:

>>> def foo(bar=[]): # bar is optional and defaults to [] if not specified... bar.append("baz") # but this line could be problematic, as we'll see...... return bar

看出bug了吗?那就是在每次调用函数前没有对可变参数进行赋值,而认为该参数就是默认值。比如上面的代码,有人可能期望在反复调用foo()时返回'baz',以为每次调用foo()时,bar的值都为[],即一个空列表。

但是,让我们来看看代码运行结果:

>>> foo()["baz"]>>> foo()["baz", "baz"]>>> foo()["baz", "baz", "baz"]

嗯?为什么每次调用foo()后会不断把"baz"添加到已有的列表,而不是新建一个新列表呢?答案就是,函数参数的默认值仅在定义函数时执行一次。因此,仅在第一次定义foo()时,bar初始化为默认值(即空列表),此后,每次调用foo()函数时,参数bar都是第一次初始化时生成的列表。

常见的解决方案:

>>> def foo(bar=None):... if bar is None: # or if not bar:... bar = []... bar.append("baz")... return bar...>>> foo()["baz"]>>> foo()["baz"]>>>foo()["baz"]

常见错误2:错误地使用类变量

代码示例:

>>> class A(object):... x = 1...>>> class B(A):... pass...>>> class C(A):... pass...>>> print A.x, B.x, C.x1 1 1

运行结果没问题。

>>> B.x = 2>>> print A.x, B.x, C.x1 2 1

结果也正确。

>>> A.x = 3>>> print A.x, B.x, C.x3 2 3

什么鬼?我们只改变了A.x.,为什么C.x 也变了?

在Python中,类变量是以字典形式进行内部处理,遵循方法解析顺序(Method Resolution Order ,MRO)。因此,在上述代码中,因为在类C中没有找到属性x,它就会从父类中查找x的值(尽管Python支持多重继承,但上述代码只存在一个父类A)。换句话说,C没有独立于类A的属于自己的x。因此,C.x实际上指的是A.x。除非处理得当,否则就会导致Python出现错误。

如果想更深入了解Python的类特性,请戳:

https://toptal/python/python-class-attributes-an-overly-thorough-guide

常见错误3:错误指定异常代码块的参数

假设你有如下代码:

>>> try:... l = ["a", "b"]... int(l[2])... except ValueError, IndexError: # To catch both exceptions, right?... pass...Traceback (most recent call last):File "", line 3, in IndexError: list index out of range

这里的问题是except语句不接受以这种方式指定的异常列表。在Python2.x中,except Exception语句中变量e可用来把异常信息绑定到第二个可选参数上,以便进一步查看异常的情况。因此,在上述代码中,except语句并没有捕捉到IndexError异常;而是将出现的异常绑定到了参数IndexError中。

想在一个except语句同时捕捉到多个异常的正确方式是,将第一个参数指定为元组,并将要捕捉的异常类型都写入该元组中。为了方便起见,可以使用as关键字,Python 2 和Python 3都支持这种语法格式:

>>> try:... l = ["a", "b"]... int(l[2])... except (ValueError, IndexError) as e: ... pass...>>>

常见错误4:错误理解Python中变量的作用域

Python变量作用域遵循LEGB规则,LEGB是Local,Enclosing,Global,Builtin的缩写,分别代表本地作用域、封闭作用域、全局作用域和内置作用域,这个规则看起来一目了然。事实上,Python的这种工作方式较为独特,会导致一些编程错误,例如:

>>> x = 10>>> def foo():... x += 1... print x...>>> foo()Traceback (most recent call last):File "", line 1, in File "", line 2, in fooUnboundLocalError: local variable 'x' referenced before assignment

问题出在哪?

上面的错误是因为在作用域内对变量赋值时,Python自动将该变量视为该作用域的本地变量,并对外部定义的同名变量进行了屏蔽。因此,原本正确的代码,在某个函数内部添加了一个赋值语句后,却意外收到了UnboundLocalError的报错信息。

关于UnboundLocalError更多内容请戳:

https://docs.python.org/2/faq/programming.html#why-am-i-getting-an-unboundlocalerror-when-the-variable-has-a-value

在使用列表时,Python程序员更容易掉入此类陷阱,例如:

>>> lst = [1, 2, 3]>>> def foo1():... lst.append(5) # This works ok......>>> foo1()>>> lst[1, 2, 3, 5]>>> lst = [1, 2, 3]>>> def foo2():... lst += [5] # ... but this bombs!...>>> foo2()Traceback (most recent call last):File "", line 1, in File "", line 2, in fooUnboundLocalError: local variable 'lst' referenced before assignment

奇怪,为什么foo1正常运行,而foo2崩溃了呢?

原因和上一个案例中出现的问题相似,但这里的错误更加细微。函数foo1没有对变量lst进行赋值操作,而函数foo2有赋值操作。

首先, lst += [5]是lst = lst + [5]的缩写形式,在函数foo2中试图对变量lst进行赋值操作(Python将变量lst默认为本地作用域的变量)。但是,lst += [5]语句是对lst变量自身进行的赋值操作(此时变量lst的作用域是函数foo2),但是在函数foo2中还未声明该变量,所以就报错啦!

常见错误5:在遍历列表时修改列表

下面代码中的错误很明显:

>>> odd = lambda x : bool(x % 2)>>> numbers = [n for n in range(10)]>>> for i in range(len(numbers)):... if odd(numbers[i]):... del numbers[i] # BAD: Deleting item from a list while iterating over it...Traceback (most recent call last):File "", line 2, in IndexError: list index out of range

有经验的程序员都知道,在Python中遍历列表或数组时不应该删除该列表(数组)中的元素。虽然上面代码的错误很明显,但是在编写复杂代码时,资深程序员也难免会犯此类错误。

幸好Python集成了大量经典的编程范式,如果运用得当,可以大大简化代码并提高编程效率。简单的代码会降低出现上述bug的几率。列表解析式(list comprehensions)就是利器之一,它将完美避开上述bug,解决方案如下:

>>> odd = lambda x : bool(x % 2)>>> numbers = [n for n in range(10)]>>> numbers[:] = [n for n in numbers if not odd(n)] # ahh, the beauty of it all>>> numbers[0, 2, 4, 6, 8]

更多有关列表解析式的详细内容,请戳:https://docs.python.org/2/tutorial/datastructures.html#tut-listcomps

常见错误6:不理解Python闭包中的变量绑定

代码示例:

>>> def create_multipliers():... return [lambda x : i * x for i in range(5)]>>> for multiplier in create_multipliers():... print multiplier(2)...

你以为运行结果会是:

02468

但实际输出结果是:8

8888

惊不惊喜!

这种情况是由于Python延迟绑定(late binding)机制造成的,也就是说只有在内部函数被调用时才会搜索闭包中变量的值。所以在上述代码中,每次调用create_multipliers()函数中的return函数时,会在附近作用域中查询变量i的值。(此时,return中循环已结束,所以i值为4)。

常见解决方案:

>>> def create_multipliers():... return [lambda x, i=i : i * x for i in range(5)]...>>> for multiplier in create_multipliers():... print multiplier(2)...02468

没错!我们利用了匿名函数lambda的默认参数来生成结果序列。有人觉得这种用法很简洁,有人会说它很巧妙,还有人会觉得晦涩难懂。如果你是Python开发人员,那么深刻理解上述语法对你而言非常重要。

常见错误7:模块之间出现循环依赖

假设你有两个文件,分别是a.py和b.py,两者相互导入,如下所示:

a.py模块中的代码:

import bdef f():return b.xprint f()

b.py模块中的代码:

import ax = 1def g():print a.f()

首先,我们尝试导入a.py:

>>> import a1

运行结果正确!这似乎有点出人意料,因为我们在这里进行循环导入,应该会报错呀!

答案是,在Python中如果仅存在一个循环导入,程序不会报错。如果一个模块已经被导入,Python会自动识别而不会再次导入。但是如果每个模块试图访问其他模块不同位置的函数或变量时,那么Error又双叒叕出现了。

回到上面的示例中,当导入a.py模块时,程序可以正常导入b.py模块,因为此时b.py模块未访问a.py中定义任何的变量或函数。b.py模块仅引用了a.py模中的a.f()函数。调用的a.f()函数隶属于g()函数,而a.py或b.py模块中并没有调用g()函数。所以程序没有报错。

但是,如果我们在未导入a.py模块之前先导入b.py模块,结果会怎样?

>>> import bTraceback (most recent call last):File "", line 1, in File "b.py", line 1, in import a File "a.py", line 6, in print f() File "a.py", line 4, in f return b.xAttributeError: 'module' object has no attribute 'x'

报错了!问题在于,在导入b.py的过程中,它试图导入a.py模块,而a.py模块会调用f()函数,f()函数又试图访问b.x变量。但此时,还未对变量b.x进行定义,所以出现了AttributeError异常。

稍微修改下b.py,即在g()函数内部导入a.py就可以解决上述问题。

修改后的b.py:

x = 1def g():

import a # This will be evaluated only when g() is calledprint a.f()

现在我们再导入b.py模块,就不会报错啦!

>>> import b>>> b.g()1 # Printed a first time since module 'a' calls 'print f()' at the end1 # Printed a second time, this one is our call to 'g'

常见错误8:文件命名与Python标准库模块的名称冲突

Python的优势之一就是其集成了丰富的标准库。正因为如此,稍不留神就会在为自己的文件命名时与Python自带标准库模块重名。例如,如果你的代码中有一个名为email.py的模块,恰好就和Python标准库中email.py模块重名了。)

上述问题比较复杂。举个例子,在导入模块A的时候,假如该模块A试图导入Python标准库中的模块B,但你已经定义了一个同名模块B,模块A会错误导入你自定义的模块B,而不是Python标准库中的模块B。这种错误很糟糕,因为程序员很难察觉到是因为命名冲突而导致的。

因此,Python程序员要注意避免与Python标准库模块的命名冲突。毕竟,修改自己模块的名称比修改标准库的名称要容易的多!当然你也可以写一份Python改善建议书(Python Enhancement Proposal,PEP)提议修改标准库的名称。

常见错误9:不熟悉Python2和Python3之间的差异

先来看看foo.py文件中的代码:

import sysdef bar(i):if i == 1: raise KeyError(1) if i == 2: raise ValueError(2)def bad(): e = None try: bar(int(sys.argv[1])) except KeyError as e: print('key error') except ValueError as e: print('value error') print(e)bad()

在Python 2中,上述代码运行正常

$ python foo.py 1key error1$ python foo.py 2value error2

但是在Python 3中运行时:

$ python3 foo.py 1key errorTraceback (most recent call last):File "foo.py", line 19, in bad() File "foo.py", line 17, in bad print(e)UnboundLocalError: local variable 'e' referenced before assignment

什么情况?原来,在Python 3中,在except代码块作用域外无法访问异常对象。(原因是,Python 3会将内存堆栈中的循环引用进行保留,直到垃圾回收...

img

在线咨询

建站在线咨询

img

微信咨询

扫一扫添加
动力姐姐微信

img
img

TOP