Python物理学高效计算

978-7-115-47078-2
作者: 【美】Anthony Scopatz(斯科普斯) Kathryn D. Huff(赫夫)
译者: 孙波翔
编辑: 武晓燕陈冀康
分类: Python

图书目录:

详情

本书介绍了物理学领域中需要用到的基本软件开发技能,可用来帮助读者以自动化的方式完成物理领域方面的研究。全书共分为4个部分。第一部分:起步,介绍Python的基本知识。第二部分:上手,主要介绍正则表达式、数据可视化、存储数据等实用知识。第三部分:完善,介绍如何构建流程和软件、版本控制、调试和控制代码等。第四部分发布,介绍如何为代码生成文档、如何提高协作效率和软件许可证以及版权的相关知识等。

图书摘要

版权信息

书名:Python物理学高效计算

ISBN:978-7-115-47078-2

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

• 著    [美] 安东尼•斯科普斯(Anthony Scopatz),

       凯瑟琳•赫夫(Kathryn D.Huff)

  译    孙波翔

  责任编辑 陈冀康

• 人民邮电出版社出版发行  北京市丰台区成寿寺路11号

  邮编 100164  电子邮件 315@ptpress.com.cn

  网址 http://www.ptpress.com.cn

• 读者服务热线:(010)81055410

  反盗版热线:(010)81055315


Copyright © 2015 by O’Reilly Media, Inc.

Simplified Chinese Edition, jointly published by O’Reilly Media, Inc. and Posts & Telecom Press, 2017. Authorized translation of the English edition, 2017 O’Reilly Media, Inc., the owner of all rights to publish and sell the same.

All rights reserved including the rights of reproduction in whole or in part in any form.

本书中文简体版由O′Reilly Media, Inc.授权人民邮电出版社出版。未经出版者书面许可,对本书的任何部分不得以任何方式复制或抄袭。

版权所有,侵权必究。


本书介绍了如何通过Python自动化地完成物理领域方面的研究。全书共分为4个部分,以Python代码为示例向读者介绍了如何用Python解决物理项目中出现的各种问题。第1部分(第1~6章):起步,介绍Python的基本知识,如命令行、数据容器、类和对象等。第2部分(第7~13章):上手,主要介绍正则表达式、数据可视化、存储数据等实用知识。第3部分(第14~18章):完善,介绍如何构建流程和软件、版本控制、调试和控制代码等。第4部分(第19~23章):发布,介绍如何为代码生成文档、如何提高协作效率和软件许可证以及版权的相关知识等。

本书适合想要通过Python减少工作量的物理学领域的研究人员阅读,也适合想要学习如何通过Python编程解决物理问题的读者参考。


此时此刻,某地一个学生正在痛苦地调整一堆格式混乱的数据,这些数据位于一堆名为“最终版”“最终修改版”“最终版再更新版”的文件夹中。在旁边,她的导师刚花了4小时来修改6个月前编写的论文中的图表,以满足另一位审稿人对论文提出的修改要求。在大厅里,实验室里的实习生需要处理200个输入文件,他正在GUI软件上用鼠标单击分析第35个,但直到几天后他才会意识到用错了数据。

这些根本不是科学,只是科学工作者由于缺乏基本的用于科学计算的实验技能而在做一些重复劳动。他们花费了大量时间来做原本可以用计算机完成的工作,或是尝试了解他们的同事上次做了些什么,而这些原本可以让计算机告诉他们。更糟的是,他们根本不知道得到的工作结果是否可靠。

与威斯康星大学的Hacker Within团队一起,Katy和Anthony会帮助读者免去这些痛苦。像命令行shell、版本控制以及编写模块化代码这样的基本技巧,可以省去科学工作者大量的时间,同时有助于帮助他人在将来重现并构建手头的工作。

这本书并不会让读者成为一个伟大的开发者,但会让读者更加优秀。本书会帮助读者逃出泥潭,提供一些背景知识并让读者学会使用网上教程的问答社区。我真希望这本书是我写的,但我写出来的质量可能没有Anthony和Katy写得这么好。愿读者像我一样能用好本书。

——Gregory V. Wilson


欢迎阅读本书。本书将介绍物理学领域中需要用到的基本软件技能。囊括了从天体物理学到核工程等诸多领域,本书会将读者从不知道如何在计算机上将两个变量相加的新手,培养成团队中的软件开发大师。

物理与计算都有悠久的历史。在许多方面,计算机和现代物理学已经共同演变。只有密码学才能真正像物理学那样跟上计算机的发展。虽然共同成长,但物理学家并不是头等软件开发人员。物理学家通常会由于两个错误观点而吃亏:

1.软件开发和软件工程很容易;

2.会物理的人也会编写代码。

虽然一些技能可以互相转换,例如理解抽象符号对于物理和软件开发都很重要。但两者的基本关注点、需求、兴趣、结果的推导机制往往有很大区别。对于物理学家来说,计算机只是工具箱中的一个工具。在物理学中,计算的作用与数学的作用不同。人们可以在没有计算机的情况下了解物理概念,但使用计算机语言能够简化物理处理过程。此外,与计算尺、光子检测器、示波器都不相同,物理计算机是一个实验设备,正确设置后就能方便地辅助科学研究。由于计算机比上述实验装置都复杂也更可配置,所以需要更多的耐心、照料、理解,最终才能正确设置。

越来越多的物理学家需要在工作或研究中扮演软件开发人员的角色。这本书旨在帮助软件开发人员尽可能地轻松成长。长此以往,让物理学家更有成效地工作。

在另一方面,计算建模和模拟仿真已经在物理学中发挥着重要作用。当实验规模太大或太昂贵时,或者理论参数受限时,模拟科学就能够发挥重要作用。模拟能够帮助实验者在实践之前先对理论进行验证。模拟正在成为物理学家理论和实验的中间环节。许多模拟科学家更喜欢认为自己是偏理论的。实际上,模拟中用到的方法更接近实验主义。

所有现代物理学家无论以什么方式做实验,都会在科学工作流程的某些部分用到计算机。有些研究人员只用计算机处理文档。而其他人可能会使用计算机不知疲倦地收集数据,并在夜间进行数据分析,其效率超过研究团队中的大部分成员。本书介绍利用计算机实现并自动化几乎所有研究内容,在研究的每个阶段都能将本书作为指南来使用。

本书从各个角度介绍计算物理学。本书将帮助读者获得和锻炼对物理来说无价的软件开发技能。据我们所知,还不存在其他这样的图书。本书既不是物理教科书,也不是学习Python和其他编程概念的唯一方法。本书介绍当物理与编程相遇时会发生的事情,即计算物理学。相信你会喜欢的!

这本书适用于物理学领域需要进行一些开发工作的人。这里的“物理学领域”涵盖的范围比较广,包括物理学、天文学、天体物理学、地质学、地球物理学、气候科学、应用数学、生物物理学、核工程、机械工程、材料科学和电气工程等领域。本书中的“物理”一词,指的是这些广义上的物理和工程学,不是某个单一的研究领域。

虽然本书使用Python编程语言介绍,但书中的概念也适用于其他编程语言。这里选择Python是因为在各种情况下都能简单、直观地使用这门语言。当尝试学习计算物理学中的概念时,Python会脱颖而出。读者可以把在这里学到的技能应用到其他编程环境中。

虽然欢迎任何人阅读本书并学习,但这本书的主要目标是让物理学者学习计算技能。书中的例子来自物理概念的实用知识。如果读者的主要工作是语言学家或人类学家,那么这本书可能不适合你。阅读本书不需要任何计算机或编程的知识。如果读者已经作为软件开发人员工作了好几年,那么这本书的作用就非常小。

下面用一个例子来说明本书的用法。假设一个物理团队使用一个新的探测器来测量较高精度的镭同位素衰变常数。物理学家需要访问接收到的数据。他们可能想写一个小程序,将每个同位素的预期衰变情况作为时间当变量的函数。接着,科学家将从检测器收集实验数据,存储原始输出,然后将其与预期值进行比较,并发布关于比较差异的论文。由于科学家遵循科学宗旨并尊重他们的同事,因此一定会亲自测试所有的分析,并仔细地记录每一步过程。毕竟他们的同事需要为核素表中的数千个其他同位素重复这个过程。

为了访问拥有核数据的库,例如对于每个同位素i当前接收到的的核衰变常数λi,科学家可能必须在文件系统中安装ENSDF数据库。这需要用到shell(第1章)和构建软件系统(第14章)中介绍的知识。

同位素预期活动可作为时间的函数,该函数非常简单。但不管方程如何简单,没有人愿意每10-10秒就手工解算一次方程(或在Excel中复制和粘贴)。为此,第2章介绍了在Python编程语言中创建简单函数。而对于更复杂的数学模型,可能需要面向对象(第6章)、数值Python(第9章)和数据结构(第11章)方面的知识。

成熟的实验无需人为干预。换句话说,物理学家可以在家里睡觉,而实验室整夜无拘无束地运行着实验。在第1章和第2章中学习的技能可以帮助在实验中自动收集数据。而存储数据的方法需要用到第10章介绍的HDF5相关的知识。

当获得模拟的值并收集完实验数据后,下一步是比较两个数据集。除了从第1章和第2章中学习到的知识以外,这一步还需要用复杂的工具来分析和可视化数据(第7章)。对于非常复杂的数据分析,通过并行(在第12章中介绍相关基础知识)同时使用多个处理器来加快工作速度。

在科学中,重现性至关重要。为了确保科学家们可以重现软件结果,将分析流程回滚到以前的版本并重新绘图,科学家所有以前版本的代码和数据都应该使用版本控制。这可能是本书中最重要的一个工具。第15章介绍了版本控制的基础知识,第16章介绍在协作中使用版本控制。

除了可重现之外,理论、数据收集、分析和绘图这些步骤必须确保是正确的。因此第17章将介绍如何调试软件和理解错误消息的基础知识。即使在调试之后,科学家依然担心软件中含有尚未察觉的错误,如果论文因此而撤稿,那么无疑是个灾难,因此科学家需要测试为项目编写的代码。第18章将介绍与语言无关的测试代码原则,以及用于测试Python代码的具体工具。

一直以来,物理学家都会记录计算的过程和方法。第19章介绍一些用来为代码创建用户手册的工具。该章将演示从含有注释的代码自动生成可在网上点击和发布的手册。即使在项目快结束时才开始添加文档,眼光长远的物理学家仍然可以使用第19章介绍的工具为后辈整理工作内容。在分享这些含有文档的代码时,就会用到许可证(第22章)和协作(第21章)方面的知识。

一旦软件编写完成、测试正确和添加了文档,物理学家就可以进入重要的论文编写阶段。在成功的研究计划中,最终的回报是在同行评审的出版物中分享他们的研究成果。在生成数据并根据数据绘制图表后,实际的挑战往往刚刚开始。幸运的是,第20章将介绍能够提高作者科学文件编写效率的工具。

读者应该通过实践学习。笔者希望读者能够学到知识,因此需要跟着书中的例子练习。书中的例子贴近实际,并不是理论性的。在与Python相关的章节中,将学会启动一个Python会话。尝试自己编写示例中的代码,并修改书中的示例。自己编写代码能够加深对软件和物理的印象。

如果读者遇到问题,首先请尝试自己解决问题。读者也可以在网上搜索碰到的问题。问答网站Stack Overflow也能帮到你。如果还是没有进展,请随时与我们联系。本书只介绍了有限的知识。但只要读者有目标和想象力,就能不断学到计算物理学的新知识。

此外,读者可以随时跳过已经熟悉,或与工作无关的章节或主题。如果读者对某些内容不熟练或需要复习,可以随时回头来阅读。全书插入了很多前后引用的参考,所以不用担心跳过了重要的东西。书中将相关内容放在一起,以便了解相关内容的前因后果。这本书既是一个人的科学之旅,也是一本参考手册。请读者同时以这两种方法使用本书。

本书中使用以下印刷约定:

斜体

表示网址、电子邮件地址、文件名和文件扩展名。

等宽字体

用于程序代码,以及段落中的引用程序元素,如变量或函数名、数据库、数据类型、环境变量、语句和关键字。

 

表示提示或建议。

 

 

表示一般注释。

 

 

表示警告。

本书还使用了相当数量的“代码标注”。这是编码示例用数字注释的地方。例如:

print("This is code that you should type.")(1)

(1)用来对正在编写的软件进行说明。

这种方式可以帮助读者关注代码中的特定部分,并逐步解释发生的事情。读者输入代码时不用输入这些数字,这些数字并不是代码的一部分。

读者可从https://github.com/physics-codes/examples下载补充材料(代码示例、练习等)。

这本书是为了帮助读者完成相关的工作。一般来说,读者可以在程序和文档中使用本书提供的示例代码。除非需要复制代码的重要部分,否则无需与我们联系。例如,编写一个程序,其中用到了本书中多个代码块是无需许可。而出售或分发含有从O'Reilly的书中示例的CD-ROM的例子需要许可。引用本书和引用示例代码来回答问题不需要许可。将本书中的大量示例代码并入产品文档中需要获得许可。

我们不要求标明出处,但如果标出的话,我们对此表示赞赏。出处通常包括标题、作者、出版商和ISBN。例如:“Effective Computation in Physics by Anthony Scopatz and Kathryn D. Huff (O’Reilly). Copyright 2015 Anthony Scopatz and Kathryn D. Huff, 978-1-491-90153-3.”

如果读者觉得自己对示例代码的使用方式不符合上面介绍的情形,可随时通过permissions@oreilly.com与我们联系。

本书将介绍如何使用和掌握许多不同的软件项目。这意味着读者必须在计算机上拥有大量软件包才能跟着本书学习。幸运的是,近年来安装软件包的过程已经大大简化了。本书将使用conda包管理器来安装所需的软件。

读者需要下载并安装Miniconda,或者安装Anaconda。Miniconda是Anaconda的精简版本,两者只要安装一个就可以了。Miniconda是Conda附带的Python发行版,本书将使用这个工具安装所需要的所有其他东西。从Conda网站可以下载到适合读者所用系统的Miniconda版本。包括Linux、Mac OS X、Windows版本,32位和64位体系结构。安装Miniconda时并不需要拥有计算机的管理员权限。虽然本书中的所有示例都可用于Python 2,但笔者建议读者安装Python 3版本。

如果读者在Windows上,建议使用Anaconda,因为该工具可解决其他软件包中的一些安装问题。在Windows上也可以双击Miniconda的可执行文件,按照安装向导中的说明来安装Miniconda。

 

如果在Windows上没有安装Anaconda,请安装msysGit和Git Bash

如果读者在Windows上没有安装Anaconda,请先下载并安装msysGit(可在GitHub上找到)。这款工具提供了名为Git的版本控制系统以及bash shell,书中会介绍这两个工具。Windows上没有提供msysGit,也无法通过Miniconda安装。msysGit默认的安装设置就应该足够满足这里的需求了。

如果读者在Linux或Mac OS X上,首先打开终端应用程序。如果读者不知道终端的位置,请使用操作系统的搜索功能查找。打开终端后,在美元符号($)后输入以下内容。注意,读者可能需要更改文件名(即Miniconda-3.7.0-Linux-x86_64.sh这部分)中的版本号,以匹配所要下载的文件:

# On Linux, use the following to install Miniconda:
$ bash ~/Downloads/Miniconda-3.7.0-Linux-x86_64.sh

# On Mac OS X, use the following to install Miniconda:
$ bash ~/Downloads/Miniconda3-3.7.0-MacOSX-x86_64.sh

这里已经将Miniconda下载到默认的下载目录〜/ Downloads中,下载的文件是64位版本,如果读者使用的是32位版本,则必须相应地调整文件名。

在Linux、Mac OS X、Windows上,当安装程序询问是否要自动更改或更新.bashrc文件或系统PATH时,选择是。这将使Miniconda自动进入系统环境,简化将来的安装步骤。所有其他安装选项使用默认设置即可。

安装完Conda后就可以安装本书所需的软件包。在Windows上请打开命令提示符cmd.exe。在Linux和Mac OS X上请打开终端。读者可能需要打开一个与安装Miniconda不同的新终端窗口才能生效。现在无论读者的操作系统是什么,都可以输入以下命令:

$ conda install --yes numpy scipy ipython ipython-notebook matplotlib pandas \
pytables nose setuptools sphinx mpi4py

可能需要几分钟下载相关软件,之后就可以开始工作了!

Safari 在线图书(Safari Books Online)是一家按需服务的数字图书馆,提供来自出版商的技术类和商业类专业参考书目和视频。

专业技术人员、软件开发人员、Web设计师、商业和创意专家将Safari Books Online作为他们研究、解决问题、学习和认证培训的主要资源。

Safari Books Online为组织、政府机构和个人提供一系列的产品组合和定价计划。用户可以在一个来自各个出版社的完全可搜索的数据库中访问成千上万的书籍、培训视频和正式出版前的手稿,这些出版社包括:O’Reilly Media、Prentice Hall Professional、Addison-Wesley Professional、Microsoft Press、Sams、Que、Peachpit Press、 Focal Press、Cisco Press、John Wiley & Sons、Syngress、Morgan Kaufmann、IBM Redbooks、Packt、Adobe Press、FT Press、Apress、Manning、New Riders、McGraw-Hill、Jones & Bartlett、Course Technology等。欲获得有关Safari Books Online的更多信息,请在线访问我们。

美国:

  O’Reilly Media, Inc.

  1005 Gravenstein Highway North

  Sebastopol, CA 95472

中国:

  北京市西城区西直门南大街2号成铭大厦C座807室(100035)

  奥莱科技术咨询(北京)有限公司

我们有个关于本书的网页,上面有勘误表、示例和所有的附加信息。可以通过以下链接访问:http://bit.ly/thoughtful-machine-learningwith-python

关于本书的评论和技术问题,请发邮件给bookquestions@oreilly.com

关于本书的更多信息,如教程、会议、新闻,请参见网站:

http://www.oreilly.com

http://www.oreilly.com.cn

衷心感谢Greg Wilson和Software Carpentry。你们所做的工作深刻影响了计算科学,为本书的出现创造了条件,对社会做出了不可低估的巨大贡献。

同样,还要感谢Paul P.H. Wilson和Hacker Within这些年对我们的支持。您一直力争大家不要被职位和年龄束缚,彼此之间互相学习,了解之前不知道的东西。

还要特别感谢Stephen Scopatz和Bruce Rowe提供的无微不至的帮助。没有这些帮助,这本书将永远不会出现。

非洲数学科学研究所对科学计算起到了巨大帮助,即使我们这样已经工作多年的人也是如此。你们的工作启发了这本书,我们希望这本书能回馈你们的学生。

我们还要感谢本书的审核人员:Jennifer Klay、Daniel Wooten、Michael Sarahan、DeniaDjokić。

向世界各地咖啡馆的咖啡师致敬。



命令行,也称shell,为用户和计算机内部之间提供了功能上强大且透明的接口。至少在Linux或UNIX中,命令行可以无障碍访问决定当前计算机状态的文件和进程(包括操作系统本身的文件和进程)。

通过命令行可以安装和运行许多与物理相关的数值工具。命令行这种直观的接口不仅能提升物理学家对物理的好奇心,而且有些工作只能通过命令行来完成。对读者来说,命令行可能像黑客帝国中的充满绿色字符的画面,但不要让电影误导了你。下面来了解真实的命令行。

在Linux或UNIX上可以打开一个终端模拟器(termimal emulator,简称为终端)来访问shell,而在Windows上有类似的Git Bash软件。启动终端会打开一个交互式的shell程序,它可以运行可执行程序。shell提供了命令行界面(command-line interface,用来运行命令以及浏览计算机中的文件系统(只要该shell连接上这台计算机即可)。命令行有时也称为“提示符”(prompt),在本书中以美元符号($)表示,表示在光标处可以输入内容,如图1-1所示。

shell强大且透明,能够无障碍地访问计算机上的文件和进程。那么shell到底是什么呢?

图1-1 一个终端实例

shell是由终端运行的编程语言。与其他编程语言相似,shell有以下特点。

编程语言种类繁多,shell同样如此。在众多shell中,本书将使用最常见的bash。除此之外,还有csh、tcsh和ksh也很流行。表1-1列出了这些shell的特性。

表1-1 Shell的种类

Shell

名 称

说  明

sh

Bourne shell

最著名的shell,开发于1977年,至今所有UNIX系统上都有安装

csh

C shell

由sh改进而来

ksh

Korn shell

反向兼容sh,但借鉴了其他shell的一些特点

bash

Bourne again shell

sh的自由软件替代品,但进行了大范围改进

tcsh

Tenex C shell

由C shell更新扩展而来

 

 

练习:打开一个终端

1.在计算机上找到一个名为Terminal的程序。在Windows上使用Git Bash作为bash终端。

2.打开该程序进入shell。

shell强大之处在于其透明性,即shell可以直接访问整个文件系统,因此几乎能够完成所有任务,如查找文件、处理文件、安装软件库,以及在终端中运行提供了软件路径的程序。

文件的所在位置是由一系列嵌套的目录(文件夹)组成的。按照UNIX的说法,每个目录的位置(以及其中的文件)都由路径来指定。这种路径可以是绝对路径,也可以是相对路径

如果路径是从文件系统目录树的顶部开始,那么即为绝对路径。文件系统目录树的顶端称为根目录。根目录的路径为/。因此绝对路径都以/开头。

在许多UNIX和Linux系统中,根目录中含有bin和lib这样的目录。bin和lib目录的绝对路径分别为/bin和/lib。图1-2显示了一个目录树的示例图,其中包含了一些路径的表示方法。

 

“/”语法除了在路径起始处表示顶层目录外,还可用来分割路径中目录的名称,如图1-2所示。

图1-2 目录树示例

路径也可以用与当前工作目录的相对位置来表示。当前工作目录以一个点表示(.),该目录的父目录以两个点表示(..)。因此,相对路径通常以这两者开头。

前面已经提到,绝对路径描述的是某个文件相对于根目录的位置。而相对路径描述的是与当前工作目录的相对位置。将两者的概念统一起来,用户可以用pwd(print working directory,当前工作目录)命令打印出当前工作目录完整的绝对路径。

20世纪30年代,Lise Meitner发现中子诱发裂变的理论框架时,Bash还未诞生。如果能够使用Bash,Meitner教授的研究计算机也许会包含一系列的目录,其中含有她关于裂变理论的文件,以及关于理论应用方面的文件(参见图1-2)。现在来看一下Lise会如何组织这个目录结构。

 

在本书中读者会跟着Lise一起操作。同时这一章用到的目录树在GitHub上会作为一个库发布。读者可以根据GitHub上页面中的指示来下载相关文件。

当Lise工作时,她会在命令提示符中输入命令。在下面的例子中,可以看到命令提示符在美元符号前面有一个简写的路径名称(有时会是其他内容)。该路径为~/fission,因为fission是Lise的当前工作目录。

~/fission $

当Lise在这个命令提示符后输入pwd并按回车后,shell会在当前行的下一行输出当前目录的完整路径。

~/fission $ pwd
/filespace/people/l/lisemeitner/fission/

比较这里的绝对路径与对应的缩写提示符,会发现在提示符中,当前目录之前的所有目录(包括lismeitner目录),都替换为一个波浪线(~)。在下一节会解释这个波浪线。

shell会从一个称为主(home)目录的特殊目录来启动会话。波浪线(~)字符用来表示主目录。因此,登录后会看到命令行提示符告知用户现在正处于主目录中。

~ $

提示符并不是千篇一律的,有时也会显示用户名和计算机名。

<user>@<machine>:~ $

Lise拥有著名的凯撒·威廉研究院(Kaiser Wilhelm)的研究职位,因此她使用的提示符也许会是:

meitner@kaiser-wilhelm-cluster:~ $

回到前面的例子中,比较一下目录:

~/fission

/filespace/people/l/lisemeitner/fission

看上去波浪线完全替换掉了主目录的路径(/filespace/people/l/lisemeitner)。确实,波浪下是主目录路径的缩写,即从根目录开头到主目录的字符串。由于该路径是针对目录树顶层的相对位置,因此:

~/fission

/filespace/people/l/lisemeitner/fission

都是绝对路径。

 

练习:找到主目录

1.打开终端。

2.在命令提示符中输入pwd并按下回车,查看主目录的绝对路径。

现在Lise知道她在文件系统中的位置,同时也很好奇这里藏了些什么东西。为了列出一个目录下的所有内容,Lise需要ls这个命令。

ls命令能够让用户打印出一个目录下所有的文件和子目录。

 

练习:列出一个目录的所有内容

1.打开终端。

2.在命令提示符中输入ls并按回车键,查看主目录的内容。

在Lise的主目录下的fission目录中,ls会以列表的形式输出其内容:

~/fission $ ls (1)
applications/ heat-production.txt neutron-release.txt (2)

(1)在她的主目录下的fission目录中,输入ls并按回车键。

(2)shell会列出当前目录下的内容。

当Lise列出当前目录下的内容时,会看到两个文件和一个子目录。如前面的例子所示,在shell中,目录可能会以不同的颜色显示,或在目录名的末尾加一个斜杠(/)以示区分。

Lise还可以为ls命令提供参数。若想不进入applications目录就可以列出其中的内容,可以执行下面这个命令:

~/fission $ ls applications (1)
power/ propulsion/ weapons/ (2)

(1)Lise没有进入applications目录就列出了其中的内容。

(2)shell显示了applications目录下含有的3个目录。

ls命令能够告知Lise她的文件系统中目录的内容。但为了可以实际访问这些目录,Lise需要用到cd命令。

Lise可以使用cd命令改变目录。当她只输入这两个字符时,cd命令会假设她要转到主目录,如下所示:

~/fission $ cd (1)
~ $

(1)将目录更改为默认位置,即主目录。

从这个例子中可以看到,不提供参数就执行cd会改变命令提示符。提示符反映了当前的工作目录为主目录(~)。为了核实一下,我们可以使用pwd命令就会以绝对路径的形式打印出主目录。

~ $ pwd (1)
/filespace/people/l/lisemeitner (2)

(1)打印出当前工作目录。

(2)shell显示了当前工作目录的绝对路径。

也可以为cd命令提供参数,在cd命令后提供一个参数能指定其行为:

~/fission $ cd [path]

如果Lise在cd后添加一个空格和另一个目录路径,那么shell会转到那个目录。参数既可以是绝对路径,也可以是相对路径。

 

尖括号和花括号的约定

尖括号是约定俗成的术语,其中的内容需要替换为真实值。读者不要敲入<和>之间的内容。因此,如果看到cd <argument>,应该输入类似cd mydir这样的内容。方括号([])用来表示可选的内容。同样,如果有方括号,也不要输入其中的内容。而两个方括号([[]])则表示只有在外层方括号可选参数存在的情况下,里面才会有一个可选参数。

在下面这个例子中,Lise使用绝对路径来跳转到一个子目录中。这会改变当前的工作目录,在提示符的下一行中会看到这个改动:

~ $ cd /filespace/people/l/lisemeitner/fission (1)
~/fission $ (2)

(1)Lise使用完整的绝对路径转到了fission目录。这个命令的意思是“将目录转到根目录中,然后转到filespace目录,接着转到people目录,最后转到fission目录中”,Lise输入完命令后按下了回车键。

(2)现在Lise位于~/fission目录下,提示符也有相应的改动。

当然,这样需要输入很多内容。前面学到过,波浪号(~)代表home目录绝对路径。因此用~能够方便地简化这个路径,所以前面那个非常长的路径可以替换为~/fission:

~/ $ cd ~/fission (1)
~/fission $

(1)波浪线表示home目录,所以可以缩短之前那条非常长的路径,同时结果完全相同。

另外一个简洁的方式是为cd提供相对路径作为参数。相对路径描述了某个目录与当前目录的相对位置。如果Lise想要进入当前目录的子目录,则可以省去当前目录名称之前的所有内容。因此,从fission目录到applications目录只须使用该目录的名称:

~/fission $ cd applications (1)
~/fission/applications $

(1)如果想要这条命令执行成功,要确保applications目录必须位于当前目录中。

如果目录不存在,bash就无法进入那个位置并会报告一个错误消息,如下所示。注意,读者也许已经猜到,bash会留在原来的目录中:

~/fission $ cd biology
-bash: cd: biology: No such file or directory
~/fission $

另一个有用的约定是,在使用相对路径时,当前目录可以用一个点号表示(.)。所以执行cd ./power与执行cd power完全相同:

~/fission/applications/ $ cd ./power (1)
~/fission/applications/power/ $

(1)进入applications目录,然后再进入power目录。

与之类似,当前目录的父目录用两个点表示(..)。所以如果Lise决定返回上一级,回到applications目录,需要用到下面的语法:

~/fission/applications/power/ $ cd ..
~/fission/applications/ $

有了两个点的表示方法,相对路径不仅能指向当前目录的子目录,还能够指向其他任何地方。例如,相对路径../../../表示的是当前目录往上数第三个目录。

 

练习:更改目录

1.打开终端。

2.在命令提示符中输入cd..,从主目录转到其父目录中。

3.使用相对路径转回主目录中。

4.如果读者从本书的GitHub库中下载了Lise的目录树,那么能否使用ls、cd、pwd转到那个目录中?

表1-2总结了一些路径缩写。

表1-2 路径缩写

语  法

含  义

/

文件系统的根目录或顶层目录(也可用来分割路径中目录的名称)

~

home目录

.

当前目录

..

当前目录的父目录

../..

当前目录父目录的父目录

列出文件和目录的名称的确很有帮助,但找到某个文件更多的原因是为了了解其内容。Shell为此提供了许多工具。下一节将介绍如何查看某个感兴趣文件的内容。

当在科学计算程序中处理文件的输入和输出时,通常只需要查看文件的开头或结尾(例如,检查某些重要的输入参数,或检查是否成功运行完程序)即可。head命令会输出指定文件的前10行。

~/fission/applications/power $ head reactor.txt
# Fission Power Idea
The heat from the fission reaction could be used to heat fluids. In the same way that coal power starts with the production heat which turns water to steam and spins a turbine, so too nuclear fission might heat fluid that pushes a turbine. If somehow there were a way to have many fissions in one small space, the heat from those fissions could be used to heat quite a lot of water.

读者可能已经猜到,tail命令会输出最后10行。

~/fission/applications/power $ tail(原书为head,是错的)reactor.txt
the same way that coal power starts with the production heat which turns water to steam and spins a turbine, so too nuclear fission might heat fluid that pushes a turbine. If somehow there were a way to have many fissions in one small space, the heat from those fissions could be used to heat quite a lot of water.

Of course, it would take quite a lot of fissions.

Perhaps Professors Rutherford, Curie, or Fermi have some ideas on this topic.

 

练习:查看一个文件

1.打开计算机上的一个终端程序。

2.找到一个文本文件。

3.使用headtail命令在终端中输出该文件的起始和末尾的文本。

在检查文件时,这种在终端中输出文件起始和末尾若干行的功能非常有用。学完这些内容后,我们后续的任务是创建、编辑、移动文件。

除了查找文件和目录这些简单的工作之外,shell还能用于进一步的处理,如对文件和目录进行复制、移动、删除,以及更加复杂的合并、比较、编辑等操作。接下来的章节将详细地探讨这些任务。

读者能通过以下方式来创建文件:

在编程中,每个方式都有各自的用途。

使用GUI创建文件

本书的读者有时会使用图形用户界面(GUI)来创建文件。例如用Microsoft Paint创建.bmp文件,用Word创建.doc文件。虽然这些文件不是在终端中创建的,但通常在文件系统中可见,并且能在终端中操作这些文件。但终端的功能是有限的,由于这些文件不是纯文本文件,其中含有人类不可读的二进制数据,所以必须通过GUI软件来打开。

而源代码文件是纯文本文件。不同语言的源代码文件有不同的扩展名,例如。

虽然源码文件可能有不同的扩展名,但本质上都是纯文本文件。除了专门用于创建纯文本文件的程序外,一般不应在GUI软件(如Microsoft Word)中创建纯文本文件。在创建并编辑特定语言的源代码文件时,软件开发人员经常使用交互式开发环境(IDE),即专门的GUI软件,其中含有相关语言的辅助功能,并能够生成纯文本代码文件。不同的语言选择不同的IDE。例如MATLAB可以用来创建.m文件,而IPython Notebook可用于创建.ipynb文件。

有些人借助IDE极大地提升了编码效率,而另一些人则喜欢不离开终端就能处理所有文本文件的工具。这种类型的文本编辑器是许多计算科学家的必备工具,相当于一把能锤任何类型钉子的锤子。

创建空文件(touch)

使用touch命令可以在终端中创建空的文本文件。即touch命令后跟一个文件名,就可以创建一个以该名称命名的空文件。

假设Lise想要创建一个文件用来记录核裂变应用方面的新想法,例如远距离(如向西伯利亚)提供热源。可用touch命令创建该文件:

~/fission/applications $ touch remote_heat.txt

如果文件已存在,touch命令并不会修改其中的内容。所有文件都具有元数据,此时touch只会用新的“最近编辑”时间戳更新文件的元数据。如果文件不存在,则创建新的文件。

 

注意remote_heat.txt文件的名称使用的是下划线,而不是空格。这是因为文件名中的空格在命令行中容易出错。因为命令行使用空格分隔参数,所以含有空格的文件名可能会产生歧义,因此要尽量避免在文件名中使用空格。如果一定要使用,那么转义字符(\)可以告诉shell这是文件名中的空格。比如这就是一个具有空格的文件名:my\ file\ with\ spaces \ in \ its \ name.txt。

虽然用touch可以创建空文件,但编写代码的计算科学家还需要向源码文件中添加文本,因此需要用到文本编辑器。

最简单的文本编辑器(cat和>)

在不离开终端的情况下,添加文本的最简单的方法是使用名为cat的程序和shell语法>,>语法称为重定向。

cat命令旨在将文件连接在一起。将一个文件名作为cat的参数,cat会在终端窗口打印文件的完整内容。为了输出reactor.txt中的所有内容,Lise可以像下面这样使用cat

~fission/applications/power $ cat reactor.txt

# Fission Power Idea

The heat from the fission reaction could be used to heat fluids. In the same way that coal power starts with the production heat which turns water to steam and spins a turbine, so too nuclear fission might heat fluid that pushes a turbine. If somehow there were a way to have many fissions in one small space, the heat from those fissions could be used to heat quite a lot of water.

Of course, it would take quite a lot of fissions.

Perhaps Professors Rutherford, Curie, or Fermi have some ideas on this topic.

cat的这种功能与重定向结合起来即可将一个文件的输出推送到另一个文件中。这即为重定向,顾名思义,即重定向输出。重定向的语法是大于符号(>)。箭头收集其前面命令的所有输出内容,并将其重定向到箭头后面的任何文件或程序中。如果后者是已存在的文件,则会覆盖其中的内容。如果文件不存在,则创建新的文件。例如下面的语法将reactor.txt的内容推送到名为reactor_copy.txt的新文件中:

~fission/applications/power $ cat reactor.txt > reactor_copy.txt

如果cat后面不提供任何文件名,则会从命令提示符中接收输入。

杀死或中断程序

 

在上面的练习中需要使用Ctrl+D来终止cat程序。终止程序很常见,比如有时候正在运行一个程序,但后来需要改进或发现程序不正确,需要停止执行。此时需要用到Ctrl+C来终止这些非交互式程序。而交互式程序(如less)通常定义其他快捷键来终止或退出程序,一般使用Ctrl+D。

举个例子,yes是一个永不终止的程序。如果调用yes,会在终端中无限输出字母y。用户可以使用Ctrl+C退出程序。

~/fission/supercritical $ yes
y
y
y
y
y
y
y
y
Ctrl+c

 

练习:了解命令

1.打开终端。

2.输入cat并按下回车键,光标将移动到空白行。

3.尝试输入一些文本。注意每次按回车键时都会重复显示输入的文本。

4.退出时需要按下Ctrl+D。即按住Ctrl键并同时按下大写字母D键。

使用这种方式,cat会读取命令行中输入的所有文本,并再次显示出来。这种功能与重定向结合起来,就能在命令行中向文件添加文本。因此,为了将命令行中的文本插入到remote_heat.txt文件中,可以使用以下语法:

~fission/applications/power $ cat > remote_heat.txt

输入完上述命令并按回车键后,光标将移动到空白行。此时输入的任何文本都将插入到remote_heat.txt中。若要停止添加文本并退出cat,需要按下Ctrl+D。

 

注意,如果重定向的文件不为空,则会先清空其中的内容,然后才会添加新的文本。

使用cat是向文件中添加文本的最简单的方法。但是由于cat无法让用户编辑文件,因此,它不算是一个非常强大的文本编辑器。毕竟,很难能够一次就完美地输入文件的完整内容。幸运的是,还有更强大的文本编辑器能够更有效地编辑文本。

更强大的文本编辑器(nano、emacs、vim)

使用文本编辑器能更有效地创建和编辑文件。文本编辑器是允许用户创建、打开、编辑和关闭纯文本文件的程序。文本编辑器有很多种。nano是其中一种简单的文本编辑器,建议新手用户使用。编程圈中最常见的文本编辑器是emacs和vim。这两种编辑器功能更强大,但学习曲线也更为陡峭。

在命令行中输入相应的名称就能打开对应的文本编辑器。如果文本编辑器的名称后面是已有文件的名称,则文本编辑器会打开该文件。如果文本编辑器的名称后面跟有目前不存在的文件的名称,则编辑器会创建并打开该文件。

若要使用nano文本编辑器打开或创建remote_heat.txt文件,Lise可以使用以下命令:

~fission/applications/power $ nano remote_heat.txt

图1-3显示了在终端中打开的nano文本编辑器的界面。请注意,界面底部列出了用于保存、退出和执行其他任务的快捷键命令。

图1-3 nano文本编辑器

如果Lise想要使用vim文本编辑器,则可以在命令行中以相同的方式输入vim或vi。在大多数现代的UNIX或Linux计算机上,vi是vim的简称(vim是vi improved)。若要使用emacs,需要输入emacs命令。

选择编辑器,而不是选边站队

 

数十年来,程序员一直在争论哪款文本编辑器最好,这场论战一直没有结束。在这个论战中,主要的两派分别是emacs和vim这两大先驱。而作者希望读者能够以包容的心态看待问题。比如我们要无条件地尊重每个人的生活方式,在文本编辑器的选择上也应该如此。虽然使用不同的文本编辑器在编程时会有不同的工作效率和体验。但这些选择不是一成不变的,不能因此对用户产生偏见。

由于编辑器非常强大,所以许多文本编辑器的学习曲线都很陡峭。使用强大的文本编辑器需要先掌握许多命令和快捷键。因此新手应该考虑从nano开始,nano是一种功能简单但易学的文本编辑器。

 

练习:打开nano

1.打开终端。

2.执行命令nano。

3.向文件中添加一些文本。

4.根据窗口底部的提示来命名并保存文件,然后退出nano。

现在已经知道如何创建文件,接下来学习如何移动和更改文件。使用cp命令能够创建文件的副本。cp命令的语法为cp <source> <destination>。第一个必要参数是源文件(要复制的文件),可以使用相对路径或绝对路径。第二个参数是目标文件(新的副本本身),使用相对路径或绝对路径:

~/fission/applications/power $ ls
reactors.txt
~/fission/applications/power $ cp reactors.txt heaters.txt
~/fission/applications/power $ ls
reactors.txt heaters.txt

但如果目标位于另一个目录中,则该目录必须已存在,否则cp命令将报告一个错误:

~/fission/applications/power $ cp ./reactors.txt ./electricity/power-plant.txt
cp: cannot create regular file `./electricity/power-plant.txt':
No such file or directory

如果Lise在复制文件时无需保留原始文件,则可以使用mv(move)。该命令不会复制文件,而是重命名文件。该命令名为“move(移动)”,是因为如果第二个参数是另一个目录的路径,该命令会将文件移动到那里。

假设Lisa在浏览她之前的灵感时,注意到在propulsion目录中有关于核动力飞机的文件:

~/fission/applications/propulsion $ ls
nuclear_plane.txt

核动力飞机实际上是个非常糟糕的主意。核动力飞机会笨重到无法起飞。因此Lisa决定重新命名这个文件,来告诫其他人。这个主意应该命名为bad_idea.txt。mv命令接收两个参数:一个是原始文件的路径,另一个是新的文件路径。她想将nuclear_plane.txt重命名为bad_idea.txt:

~/fission/applications/propulsion $ mv nuclear_plane.txt bad_idea.txt(1)
~/fission/applications/propulsion $ ls(2)
bad_idea.txt(3)
~/fission/applications/propulsion $ mv ./bad_idea.txt ../(4)
~/fission/applications/propulsion $ ls .(5).
bad_idea.txt power/ propulsion/ weapons/ (6)

(1)将nuclear_plane.txt移动(重命名)为bad_idea.txt。

(2)显示目录的内容。

(3)该文件现在为bad_idea.txt。

(4)尝试将bad_idea.txt移动到applications目录中。

(5)列出applications目录的内容来查看结果。

(6)重命名的文件现在位于applications目录下的propulsion目录中。

当所有文件都正确命名后,Lise可能需要新的目录来组织这些文件。此时需要 mkdir命令。

使用mkdir(make directory,创建目录文件)命令能够创建新目录。结合前面介绍的路径表示方法,用户不仅能够在当前目录中创建目录,也可以在任何地方创建目录。比如在研究关于核子的新理论时,Lise可能决定在theories目录中创建一个名为nuclear的目录。则可以使用mkdir命令以指定的路径创建一个新目录:

~/theories $ mkdir nuclear

创建时,既可以使用相对路径也可以使用绝对路径。为了在新的nuclear目录中创建一个新的目录,可以指定一个较长的路径层次:

~/theories $ mkdir ./nuclear/fission

但请注意,不能在不存在的目录中创建新文件,这个规则也适用于新目录:

~/theories/nuclear $ mkdir ./nuclear/fission/uranium/neutron-induced
mkdir: cannot create directory `./nuclear/uranium/neutron-induced':
No such file or directory

在命令行上创建这样的目录可以更好地组织文件,减少相关的开销。当然,有时可能会错误地创建文件或目录,此时需要用到rm命令。

删除文件和目录可以使用rm(remove)命令。回想一下,在applications目录中有一个记录了坏主意的文件:

~/fission/applications $ ls
bad_idea.txt power/ propulsion/ weapons/

一段时间后,Lise可能想彻底删除这个文件。为此Lise可以使用rm命令。为rm指定一个文件的路径,则rm就会删除它:

~/fission/applications $ rm bad_idea.txt

删除该文件后,用ls命令再次检查一下,可以看到该文件已经删除:

~/fission/applications $ ls
power/ propulsion/ weapons/

注意,一旦文件被删除,就会永远消失,没有在安全网、没有在垃圾箱,也没有在回收站。用rm删除的东西就真的永远消失了。

 

使用rm时要非常小心,其效果是永久的。使用rm时一定要“三思而后行”。在使用rm之前,请慎重考虑考虑是否真的要删除文件。

由于重量问题,飞机一般不会使用核能产生的热来推进,所以Lise可能决定完全删除propulsion目录。但如果只提供目录的路径,则rm命令会返回一个错误,如下所示:

~/fission/applications $ rm propulsion
rm: propulsion: is a directory

这个错误其实是rm的一个安全特性。要删除目录,需要使用-r(recursive)标志。在shell中会经常见到像-r这样会修改命令行为的命令。这个标志告诉rm进入到目录中并沿着目录树一直向下,删除propulsion目录下的所有文件和文件夹:

~/fission/applications $ rm -r propulsion

这个特性能够阻止用户在不确定的情况下删除目录树的整个分支。而这里确实希 望shell进入指定目录的所有子目录并删除其中所有内容。

在某些平台上,安全起见,rm命令在每个遇到的新子目录中都会请求确认。在删除子目录之前会询问:“rm:descend into directory'subdirectoryname'?”,输入y或n以分别确认“yes”“no”。如果将f(表示force,强行删除)添加到标志中就可以避免每次都询问。强制删除目录及其所有子目录的命令是rm -rf <directory name>

 

虽然小心地使用rm –rf能够很好地完成一些任务,但永远不要执行rm -rf *。缺德的坏人可能会推荐这一点,但这样做会有灾难性的后果。所以不要沉迷于这种愚蠢的行为。

下一节将介绍示例,其中涵盖了shell中命令的其他可用标志。

 

练习:创建和删除文件和目录

1.打开终端。

2.使用mkdir创建一个具有几个空子目录的目录。

3.使用touch在这些目录中创建5个空文件,并使用ls来核实。

4.使用单个命令(提示:需要用到递归的标志)删除整个目录。在删除时是否需要使用force标志来避免重复键入y?

在使用命令操作文件和目录时,标志(flag)通常很重要。例如移动(mv)目录时无需提供flag,但复制目录时就必须使用递归flag,否则会失败。来看一个例子,由于所有应用在做功之前都会产生热量,所以可以以power目录为蓝本创建一个名为heat的新目录:

~/fission/applications $ cp power/ heat/  
cp: omitting directory `power/'

单纯的copy命令无法用于复制目录,程序会抛出“cp:omitting directory directoryname” 错误。若要使用cp复制目录及其中的内容,必须提供-r(递归)标志:

~/fission/applications $ cp -r power/ heat/

另一种复制、移动、删除整个目录的方法是使用通配符来同时匹配多个文件。在bash shell中,星号(*)是通配符,第8章将详细讨论。现在只是先了解一下星号,大致作用就是匹配一切内容。

在下面的示例中,星号匹配了目录中的所有文件。这些文件都会复制到目标路径中:

~ $ cp beatles/ * brits/
~ $ cp zeppelin/ * brits/
~ $ cp beatles/john* johns/
~ $ cp zeppelin/john* johns/
~ $ ls brits
george jimmy john john_paul paul ringo robert
~ $ ls johns
john john_paul

但请注意,复制zeppelin/john*时会覆盖“john”目录中同名的内容。为了避免出现这样的错误,可以使用-i来交互式运行命令。此时对于任何shell认为可疑的操作,都会要求用户确认一遍:

~ $ cp beatles/john* johns/.
~ $ cp -i beatles/john* johns/.
cp: overwrite `johns/./john'? y

从某种意义上说,-i与-f相反,该标志强制shell报告可能引起警告的任何操作:

~ $ mv zeppelin/john deceased/.
~ $ mv beatles/john deceased/.
mv: overwrite `deceased/./john'? n
~ $ mv -f beatles/john deceased/.

本节介绍了命令行中常见的几个标志,但仅仅是一点毛皮。大部分命令有许多特定的行为。由于数量太多,因此很难全部记住。所以下一节介绍如何在需要的时候获取帮助,并查找更多关于各种命令的相关信息。

掌握了前面的基础知识,读者就能够在终端中自由地探索了。但在继续冒险前,还有一件最重要的事情需要解决,那就是要知道如何在需要的时候获得帮助。

man(manual)程序是在线参考手册的接口。如果将命令或程序的名称作为参数传递给man,它将打开该命令或程序的帮助文件。比如要确定ls可以使用哪些标志和选项,输入man ls就能列出其使用说明。由于man本身就是一个程序,因此可以输入man man来获取man的使用说明:

~ $ man man

NAME
    man - an interface to the on-line reference manuals
SYNOPSIS
    man [-c|-w|-tZ] [-H[browser]] [-T[device]] [-adhu7V]
    [-i|-I] [-m system[,...]] [-L locale] [-p string] [-C
    file] [-M path] [-P pager] [-r prompt] [-S list] [-e
    extension] [[section] page ...] ...
    man -l [-7] [-tZ] [-H[browser]] [-T[device]] [-p
    string] [-P pager] [-r prompt] file ...
    man -k [apropos options] regexp ...
    man -f [whatis options] page ...

DESCRIPTION
    man is the systems manual pager. Each page argument
    given to man is normally the name of a program, utility
    or function. The manual page associated with each of
    these arguments is then found and displayed. A section,
    if provided, will direct man to look only in that sec
    tion of the manual. The default action is to search in
    all of the available sections, following a pre-defined
    order and to show only the first page found, even if
    page exists in several sections.

<snip>

man后面跟着的SYNOPSIS列出了man所需的所有可选的和必要的参数、选项、变量。

参数、选项、变量

这些man手册页介绍了许多将信息传递到命令行程序中的方法,以及需要用到的命令。先来看第一个:参数。参数位于命令之后。有些命令还可以拥有多个参数。比如在切换到特定目录时(如cd ..),需要用到单个参数。而cp需要同时用到两个参数(如cp <source> <destination>)。同时也会出现ls命令带有单个参数来列出当前目录的内容:

~/weaponry $ ls .
fear ruthless_efficiency surprise

前面也见到用到选项的命令(也称为标志或开关,如递归标志,-r)。这些标志用于告知程序以某种预定义的方式运行,通常在选项前面带有减号(-)。例如,如果运行man ls并向下滚动,可以得知-r选项在ls中的作用是以相反的顺序列出目录内容:

~/weaponry $ ls -r .
surprise ruthless_efficiency fear

 

需要注意的是,同一个标志(如-r)对于不同的命令其含义不一定相同。对于大部分命令,-r表示递归行为。但对于ls,其作用是以相反的顺序打印目录内容。

变量可用于传递特定类型的信息,通常使用双减号(--)。进一步阅读ls手册页可以发现一个名为sort的变量,可以将其设置为某个值,来以各种方式对目录内容进行排序。若想为sort提供一个值,需要用到等号(=)。例如--sort = time是按文件修改时间对目录内容排序,最新文件在前:

~/weaponry $ ls --sort=time .
fear surprise ruthless_efficiency

手册页会详细介绍相关命令的所有参数、选项、变量。为了查看命令的使用方式,需要向下滚动手册页面文档,找到相关解释的位置。而为了向下滚动,就需要用到less这个很有用的命令。

在less中移动

man其实是使用一个名为less的程序打开帮助文档,用户可以在less中查看其他文本文件(只需调用less [filename])。关于less的内容很多(使用man less可以概览一下),但首先要掌握下面的内容:

less基于more这个更古老的程序。但more的功能较少,一般用不到。所以要永远记住:少即是多(less is more)。

 

练习:使用man页面中的less

1.打开终端。

2.使用man命令和前面关于less的知识来学习本章中讲到的命令(如mkdir、touch、mv、cp等)

在可以使用man和less来查找可用命令的相关信息之前,必须先要知道哪些命令是可用的。因此需要用到apropos命令。

bash shell有许多内置程序,但几乎没有人能记住所有的名字,只会在知道命令名称的情况下才能使用man手册页,因此需要一些工具来确定相关命令的名称。幸运的是,还真存在这样的工具。读者可以先在man手册页中搜索名为apropos的命令来了解一下。比如假设读者想知道有哪些文本编辑器可用,则可以搜索字符串“text editor”:

~ $ apropos "text editor" (1)
ed(1), red(1) -          text editor (2)
vim(1)                   - Vi IMproved, a programmers text editor (3)

(1)使用apropos基于关键字来搜索已安装的命令。

(2)ed和red一起出现,因为这些命令的描述信息就是“text editor”。

(3)接下来出现的是vim,其描述信息稍长。如果其他已安装的编辑器的手册页中不含有“text editor”,就不会在这里显示处理。读者可以试试apropos editor会输出什么内容。

Lise作为一个乐观的物理学家,她可能非常好奇,想要查询与物理相关的命令。但不幸的是,她可能会失望地发现没有多少相关命令:

~ $ apropos physics
physics: nothing appropriate

 

练习:查找并了解命令

1.打开终端。

2.使用apropos和关键字来搜索计算机中的命令。

3.花一些时间来熟悉前面介绍的命令,或者读者已知的其他命令和程序的man手册页。了解并尝试使用相关命令的一些新参数或选项。并在必要时练习杀死或中断程序。

现在已经介绍了运行进程和操作文件的各种命令,接下来看看如何使用重定向和管道将这些命令组成强大的流水线。  

shell的强大之处在于能够将简单的实用程序非常快地组合成更复杂的算法。其中的关键之处是shell能够将一个命令的输出发送到文件,或直接传递给另一个程序。

重定向可以将命令的输出结果发送到文件中,而不是像往常那样显示到屏幕上。箭头左侧的命令生成的文本或数据流会发送(重定向)到右侧文件中。如果右侧文件不存在的话,箭头(>)将创建一个新文件,然后用左侧命令产生的输出覆盖文件中的内容。而双箭头(>>)不会覆盖右侧文件的内容,只是将输出内容添加到文件的末尾。如果Lise想创建一个新文件,其中只包含另一个文件的第一行,她可以结合head命令和重定向方法用一行代码就能实现这个功能:

~/fission/applications/power $ head -1 reactor.txt > reactor_title.txt

现在,reactor_title.txt的内容为:

# Fission Power Idea

管道(|)命令能够将程序链接在一起,使用方法与重定向很相似。一个程序的输出可以用作另一个程序的输入。如为了将文件的中间行打印到屏幕,可以组合使用head和tail命令。比如只打印出reactor.txt文件中的第11行,Lise可以将head、tail、|这三者组合起来:

~/fission/applications/power $ head -1 reactor.txt | tail -1
Of course, it would take quite a lot of fissions.

通过这些方法,任何将文本作为输入并输出文本程序互相都可以组合起来。

现在知道了shell中许多简单的命令能够组合成有特殊用途的管道,但只有合适的权限才恰当地使用shell这种令人难以置信的组合能力。

对于在UNIX和Linux系统中使用命令、使用并共享文件来说,权限是一个微妙但重要的部分。这个主题有些晦涩,但基本思想是:对同一个文件、程序或计算机,系统对每个人能够设置不同的访问类型。

在最高级别中,只有该计算机上的用户可以访问相关文件系统。有了这些权限,就能用一些命令让用户连接到其他计算机或发送文件。例如:

只有用户拥有对应文件系统的权限时,这些命令才能工作,否则完全无法访问相关的文件系统。

当访问了一台计算机的文件系统后,每种用户的访问类型也不同。用户有3种,分别是文件的拥有者(user),拥有特殊访问权限的组(group)和其他(other)。 访问类型分别为对文件或目录进行读取(r)、写入(w)、执行(x)的权限。

本节将介绍3个用于管理文件权限的命令。

第一个ls -l是最基本的命令,用来列出文件和目录当前的权限设置。

在本章前面已经了解到ls能够列出目录的内容。而在阅读ls的手册页时,可能会注意到关于-l标志的内容,它以“长格式”列出目录内容。这种方式会列出权限相关的信息。

也就是说,如果在文件系统中的一个目录中运行ls -l,首先看到的是10个字母。比如在Lise的fission目录中,该命令会生成下面这样的“长格式”列表。其中前10位描述目录内容(文件和目录)的权限:

~/fission $ ls -l
drwxrwxr-x 5 lisemeitner expmt 170 May 30 15:08 applications
-rw-rw-r-- 1 lisemeitner expmt 80 May 30 15:08 heat-generation.txt
-rw-rw-r-- 1 lisemeitner expmt 80 May 30 15:08 neutron-production.txt

如果是一个目录,则第一位显示为d,链接为l,其他情况为-。因此applications目录的第一位是d。

要查看单个文件的权限,在ls -l命令后面直接跟该文件名:

~/fission $ ls -l heat-generation.txt
-rw-rw-r-- 1 lisemeitner expmt 80 May 30 15:08 heat-generation.txt

这个示例中仅显示了单个文件的权限。输出内容中首先是一个短划线,后面是描述heat-generation.txt文件的3组3位的字符(-rw-rw-r--)。下面来了解其中的含义:

根据这些权限设置,Lise(lisemeitner)和她的实验研究小组(expmt)可以读取或更改文件,但不能将该文件作为可执行文件来运行。而网络上的其他用户只能读取(但不能写入或运行文件)。

也就是说,这3组字符表示了用户所有者、组所有者和其他人的权限,即各自是否具有读取(r)、写入(w)、执行(x)该文件的权限。

参考ls手册页可以进一步了解ls –l显示的信息。这里在权限位后面还跟着两个名称,第一个表示用户lisemeitner,是此文件的单独所有者。第二个表示组expmt,是文件的组所有者。

 

练习:查看文件的权限

1.打开终端。

2.在命令行上执行ls -l,该命令列出了哪些内容?

3.进入/目录(执行cd /)。该目录的权限是什么?如果尝试在此目录中创建一个空文件(touch <filename>),会发生什么?

除了观察权限,修改文件系统的权限也很重要。  

在文件系统上为同事设置相关文件的权限通常很有用。在凯瑟威廉姆学院,Lise希望所有成员都可以读写她的heat-generation.txt文件。如果这些用户都是名为kwi的组的成员,则可以通过更改文件的组所有权为该组授予读写权限。用chown能够完成这个任务:

~/fission $ chown :kwi heat-generation.txt
~/fission $ ls -l heat-generation.txt
-rw-rw-r-- 1 lisemeitner kwi 80 May 30 15:08 heat-generation.txt

 

练习:更改文件的所有权

1.打开终端。

2.执行groups命令以确定所在的组别。

3.使用chown将文件的所有权更改为您所属的组。

4.重复步骤3,但将组所有权更改为之前的组别。

只更改文件的权限还不够,因为如果用户无法浏览到某个文件,那么也就不能执行这些文件。同样,如果用户用ls输出的内容无法列出某些文件,那么也就不能读取这些文件。因此,她还必须确保此组的成员可以浏览到该文件。下一节将说明如何做到这一点。

Lise必须确保她的同事可以访问并读取含有该文件的目录,她可以使用chmod更改文件模式来授予该权限。由于fission是一个目录,因此必须使用递归模式。如果kwi组的成员可以访问主目录,那么只须两个命令就可以为~/ fission下的所有内容设置权限。第一个还是chown,将fission目录的组所有者(递归地)设置为kwi:

~ $ chown -R :kwi fission/

接着用chmod更改文件模式。chmod语法为chmod [options] <mode> <path>。-R表示使用递归模式,g表示修改组的权限,加号(+)后面跟着rx表示添加读取和执行权限:

~ $ chmod -R g+rx fission/

chmod命令还有许多其他模式可用。g + rx表示向文件的组权限添加读取和执行权限。有了添加的模式,那么读者能猜出减去组读取权限的语法吗?chmod的手册页中含有更多关于设置文件权限方式的细节,读者从中可以了解一些特殊用法。

对于使用大型科学计算系统的物理学家来说,在多用户之间安全并健壮地共享数据文件和程序尤为重要。除了用这些权限来管理文件,符号链接也能用于管理跨文件系统的文件。

ln命令能够让用户创建到文件或程序的硬链接或符号链接。该命令能够高效地为一个文件创建多个引用,这些引用都指向文件的同一个存储位置。本节将重点介绍符号链接,而不是硬链接。

符号链接适用于在网络文件系统上访问大型共享资源。有了符号链接,物理学家就无需在许多地方存储大型数据集的副本。由于符号链接只占用几个字节,因此节省了硬盘空间。同时由于链接可以放置在易于找到的位置,所以相比在嵌套层数很高的子目录中搜索期望的程序和数据文件,符号链接也能节省很多时间。

对于用户来说,符号链接(用ln -s创建)是最安全有用的。比如Lise编写了一个程序,模拟铀裂变随机产生的同位素。但她的同事记不清这个程序名字是fission_fragments还是fragments。如果尝试fission_fragments,bash会报告错误,提示该命令不是有效的路径:

~/programs/fission $ ./fission_fragments
./fission_fragments: Command not found.

添加一个符号链接能够解决这个问题。即用错误的文件名指向正确文件名,创建新链接的语法为ln -s <source_path> <link_path>:

~/programs/fission $ ln -s fragments fission_fragments

执行完该命令后就创建了一个新的符号链接。用ls –l能够看到这个链接。从ls的输出来看,符号链接与其他文件很相似,只是其中有个箭头,表示它是一个指向fragments程序的指针:

~/programs/fission $ ls –l(1)
-rwxrwxr-x 1 lisemeitner staff 20 Nov 13 19:02 fragments(2)
lrwxrwxr-x 1 lisemeitner staff 5 Nov 13 19:03 fission_fragments -> fragments

(1)输入:在命令行上执行“ls –l”(即以长列表方式列出目录下的内容)命令。

(2)输出:第一行列出了fragments文件,第二行列出了fission_fragments文件,其中含有一个箭头,表示这是可执行文件的符号链接。还要注意的是,该文件的10个权限位中,第一个l表示的是“link”。

有了这个符号链接,Lise的同事也能使用fission_fragments名称了。回想一下,点(.)代表当前目录,斜线(/)用于分开目录和文件名。因此,./myfile是引用当前目录中的myfile。在当前目录中运行程序时,必须包括点划线。从下面的例子可以看出,点(.)也能用在符号链接上:

~/programs/fission$ ./fission_fragments
140Xe 94Sr

符号链接适合访问大型共享资源,此时不再需要在许多边边角角的地方存储多个副本。另一种让物理学家使用大量共享资源的常见方法是在远程机器上访问。下一节将介绍如何连接到其他计算机。

访问网络和远程文件系统是命令行中一个非常强大的功能,该功能是高性能计算任务的关键。大多数大型高性能或高吞吐量的计算资源只能在命令行中通过ssh(Secure Shell)或类似的协议进行访问。离开了shell,真正高性能的计算机系统根本无法使用。

只要有正确的凭据,就可以通过shell访问另一台机器,ssh这个程序就能做到这一点。例如,用户Grace可以用ssh命令登录到联网的计算机mk1上,后面跟着的参数表示用户名和计算机名称,用@符号隔开:

~ $ ssh grace@mk1

如果mk1是位于远程网络域名harvard.edu上的计算机,知道该计算机在其域中的完整位置,Grace就可以从家庭计算机连接到该计算机:

~ $ ssh grace@mk1.harvard.edu

登录到该计算机后,Grace就可以访问并使用远程文件系统中的文件和目录,就像在本地一样。

她可以使用scp(secure copy)命令将文件和目录从一台计算机复制到另一台计算机。语法为scp <source_file> [[user @] host]:<desti nation>。因此,为了将notes.txt文件从本地计算机复制到mk1.harvard.edu文件系统上的COBOL目录,她要执行:

~ $ scp ./notes.txt grace@mk1.harvard.edu:~/COBOL/notes.txt

 

使用ssh和scp都需要用户在远程机器上有一个有效的用户名和密码。

当Grace连接到另一台计算机时,就可以访问其中的文件系统。而在该系统中,不仅有不同的文件,还有不同的环境。下一节就将介绍环境及其配置。

bash shell除了提供命令、文件系统层次结构和导航的语法之外,还定义了一个计算环境。这个计算环境可以使用环境变量来定制。使用名为echo的程序能查看当前的环境。echo命令会将参数打印到终端。如果参数是字符串,就原样打印出来:

~ $ echo "Hello World"
Hello World

而对于环境变量,echo会展开变量,打印变量的值,而不是打印变量的名称。命令行中需要在环境变量名前面加上$。1959年,Grace Hopper开始设计第一个独立于机器的编程语言(COBOL)时,当时没有bash。毕竟,如果没有她的突破,bash就永远不会存在。但假设如果她有bash,她的环境可能像这样:

~ $ echo $USERNAME(1)
grace
~ $ echo $PWD
/filespace/people/g/grace(2)

(1)回显USERNAME环境变量的值。在某些平台上,该变量为USER。

(2)计算机将工作目录存储在环境变量PWD中,命令pwd只是echo $ PWD的快捷方式。

这些shell变量在执行时会用环境变量的值替换掉变量本身。在bash中,使用 export命令能够创建自己的变量并更改现有变量:

~ $ export GraceHopper="Amazing Grace"

变量区分大小写。因此下面的命令能正确地显示出之前赋值的字符串:

~ $ echo $GraceHopper
Amazing Grace

但以下几种都无法成功:

~ $ echo GraceHopper
~ $ echo GRACEHOPPER
~ $ echo $GRACEHOPPER

表1-3列出了一些常见且重要的shell变量。当用户从命令行编译程序和构建库时,这些变量通常对定义计算机的行为至关重要。

表1-3 常见的环境变量

环境变量名称

含  义

USER

用户名

PATH

可执行程序的搜索目录

PWD

当前目录(print working directory的缩写)

EDITOR

默认的文本编辑器

GROUP

用户所属的组

HOME

Home目录

~

与HOME相同

DISPLAY

用于通过网络连接显示图形

LD_LIBRARY_PATH

与PATH相似,但用于预编译的库

FC

Fortran编译器

CC

C编译器

环境变量可用于存储相关环境的信息,并提供长且有用的字符串(如绝对路径)的缩写。env命令能够在终端会话中查看当前所有的环境变量。Grace使用该命令可能会看到:

~/fission $ env
SHELL=/bin/bash
USER=grace
EDITOR=vi
LD_LIBRARY_PATH=/opt/local/lib:/usr/local
PATH=/opt/local/lib:/filespace/people/g/grace/anaconda/bin:/opt/local/bin
PWD=/filespace/people/g/grace/languages
LANG=en_US.utf8
PWD=/filespace/people/g/grace
LOGNAME=grace
OLDPWD=/filespace/people/g/grace/languages/COBOL

为了在每次打开新终端时自己可以定义环境变量是自动、可用的,必须将其添加到主目录中的文件,该文件必须命名为.bashrc。

bash shell中有许多文件存储了环境变量,每个终端会话中都会使用这些环境变量。这些文件是包含bash命令的纯文本文件,每次打开终端窗口时都会执行。因此,每个新的终端会话中都能使用这些文件中用export命令设置的环境变量。

为了配置和定制新的环境,可以在~/.bashrc(用户级的bash主配置文件)中添加或编辑环境变量。之前在终端中执行的export命令是为单个终端会话添加新的环境变量,而要为每个会话添加或更改环境变量,需要使用.bashrc。

 

.bashrc第一个为点号,表示文件是隐藏文件。

还有其他文件可以保存用户特定的配置。除了.bashrc文件,还有.bash_profile,或在较新的Mac OS机器上有.profile。读者的计算机上有这些文件吗?如果有的话,打开该文件查看其中是否包含文本source ~/ .bashrc。

 

练习:使用.bashrc配置shell

1.使用文本编辑器打开home目录中的.bashrc文件。如果该文件不存在,就新创建一个。

2.添加一个export命令,设置一个名为DATA的变量,表示文件系统上某些数据的位置。

3.打开一个新的终端窗口并使用echo查看DATA变量。

4.cd $ DATA的结果是什么?这种方法是不是降低了浏览的文件难度?

打开新的终端实例将自动应用.bashrc文件中的更改。而source命令能让.bashrc的改动在当前会话中立即生效,:

~ $ source .bashrc

使用.bashrc文件并执行其中的命令能最大限度地定制bash shell。这种定制通常用于指定重要的路径和默认行为,让shell更加高效和强大。在.bashrc文件中,最重要的变量是PATH。

有了环境,shell就知道在哪里可以找到在命令行中使用的命令和程序了。除非修改环境,否则无法在任意目录中运行计算机上其他位置的程序。如果要在非标准位置运行程序,则必须通过使用绝对或相对UNIX路径来告诉shell程序的位置。

例如在第14章中将学习构建一个程序,但只有提供完整的路径才能让shell运行该程序。而目前为止学习到的程序只要使用命令的名称就足够了。但由于bash只搜索特定几个目录下的可用命令,因此无法找到fragments命令:

~/programs/fission $ fragments(1)
fragments: Command not found.(2)

(1)尝试运行fragments程序。

(2)shell表示找不到该的程序(因为该程序不在PATH中)。

事实上,即使在程序所在的目录中,也必须在程序前面加上前导的点划线来指示程序的完整路径:

~/programs/fission $ ./fragments
136Cs 99Tc

为了使计算机能在不输入完整路径的情况下找到fragments程序,PATH环境变量中必须含有保存该程序的目录。若不提供路径,bash shell只能搜索PATH环境变量中指定的目录,来执行这些目录中含有的命令。为了将该文件夹添加到PATH环境变量中,Lise可以执行以下命令:

~/programs $ export PATH=$PATH:/filespace/people/l/lisemeitner/programs/fission

该命令的第一部分使用export设置PATH变量。等号右边的内容就是新PATH的内容。第一个元素是旧PATH变量值,冒号之后的第二个元素是要添加到PATH已有列表中的新目录。shell会最后一个搜索该目录。

 

练习:定制PATH

1.在终端中,使用echo查看当前PATH环境变量的值。为什么这些目录位于PATH中?

2.使用export将当前目录添加到列表的末尾。不要忘记包括PATH的原始值。

3.再次使用echo查看新值。

读者能否用PWD环境变量来缩短上述命令?除了通过设置环境变量来简化命令和路径之外,配置文件还可用于为命令提供一个别名。下一节将介绍用alias命令做到这一点。

长字符串能够用较短的变量表示(如$DATA表示数据的路径)。同样,也可以为命令创建简写别名。alias只是用第二个参数简单替换了第一个参数。例如,如果喜欢用颜色来区分shell会话中的文件和目录,那么在调用ls时总是想使用--color变量。然而,ls --color需要输入多个字符。因此最好重新设置ls的含义,让其行为类似ls --color。alias命令可以做到这一点。为了将ls替换为ls --color,需要输入:

alias ls 'ls --color'

在终端中执行该命令后,输入ls就相当于输入ls --color。与环境变量相同,为了在每次打开一个新终端时使别名可用,必须将这个定义添加到.bashrc文件中,每次登录时会执行该文件。

 

为了保持.bashrc文件简洁可读,许多用户选择将所有的别名保存在一个单独的隐藏文件(如.bash_aliases)中。然后.bashrc文件中用下面这行代码来加载该文件:

source ~/.bash_aliases

现在通过.bashrc这个bash脚本了解了自动化bash命令带来的强大功能,下一节将介绍如何编写自己的bash脚本。

通过文件(如.bashrc)存储相关命令,就能在命令行上随意重复执行这些命令。含有这些命令的文件称为脚本。这种类型的文件像一个程序,可以多次执行。

bash脚本通常以.sh扩展名结尾。因此创建bash脚本的第一步是创建一个以.sh结尾的文件。打开一个文本编辑器(如nano),并提供文件名作为参数:

~ $ nano explore.sh

终端中有效的任何命令都能够用在bash脚本中。bash脚本中可能需要用一些文本来解释说明代码的含义,这些文本(称为注释)必须用#开头。

比如Lise想要自动化浏览目录树,因此要写一个bash脚本。这是一个非常简单的bash脚本,只有简单几行。该脚本从当前目录开始,连续三次向上进入父目录并打印目录名称和其中内容:

# explore.sh (1)
# explore the three directories above this one
# print a status message
echo "Initial Directory:"
# print the working directory
pwd
# list the contents of this directory
ls
echo "Parent Directory:"
# ascend to the parent directory
cd ..
pwd
ls
echo "Grandparent Directory:"
cd ..
pwd
ls
echo "Great-Grandparent Directory:"
cd ..
pwd
ls

(1)注释前面带有#符号,只是说明用的文本,不会执行。

保存文件后,还需要一个步骤它才能成为真正的程序。为了在命令行上运行该脚本,其权限必须设置为可执行文件。因此Lise必须在终端中执行命令:

~ $ chmod a+x explore.sh

现在explore.sh脚本是可运行的。为了运行该命令,Lise必须使用文件的完整路径,或将其位置添加到她的PATH环境变量中。在使用相对路径时如下所示:

~ $ ./explore.sh

 

练习:编写一个简单的bash脚本

1.创建一个名为explore.sh的文件。

2.将示例脚本的内容复制到该文件中。

3.更改文件的权限,使其成为可执行文件。

4.运行并观察打印到终端的内容。

bash脚本中还有其他复杂的内容,但这超出了本章的范围。更多关于bash脚本的高级内容,请查看一些相关的O'Reilly书籍,或注册一个类似Software Carpentry这样网站的账号来学习。

 

历史命令

在执行完一组bash命令后,聪明的物理学家可能想要创建一个bash脚本,以便将来自动执行这组命令。此时可以用到history命令,该命令能在终端会话中列出最近执行的所有命令。用户能够用该命令回顾最近的工作,然后将其整理到一个bash脚本中。

本章仅粗略介绍了命令行的一些功能,包括:

如果读者想了解更多有关命令行的信息,可以阅读相关书籍和在线资源。Cameron Newham的《Learning the bash Shell》(O'Reilly)是一个很好的起点,而Software Carpentry网站和在线资源能够让读者进一步探索bash更深层次的秘密。


许多人都认为Python是一门伟大编程语言。没错,但什么是Python,其优点又是什么?Python是一款易学的通用动态且高阶的语言。Python能够很好地与其他语言交互(如C、C++、Fortran),因此也被称为胶水语言。由于这些优点,Python已经成为一门强大的数据分析语言。在科学和工程方面,特别是物理相关领域大放光彩。

Python主要的缺点是其性能不高。Python是一款解释型语言,与R、Ruby、Matlab相似,而与C、C++、Fortran这些编译型语言差别较大。Python用户信奉“过早优化是万恶之源”[1],通常用以下观点反驳针对Python性能的批评。

Python最重要的一点在于使用起来非常有趣。越学越想学,继而能掌握更多的内容。Python生态系统非常丰富,同时还有庞大、友善的社区。但本书无法一一介绍Python所有的优点。本章将初步介绍Python的基础语法。而关于Python安装说明,请参考前言。

Python本身是一种被称为解释器的特殊程序,这个程序将Python源代码转换成计算机处理器可以理解的指令。Python解释器有多种启动方式。最基本的方式是在终端的提示符中输入python,通常会显示一些关于Python自身的信息。接下来是以“>>>”起始的行,这是Python提示符,在此可以输入Python代码:

$ python
Python 2.7.5+ (default, Sep 19 2013, 13:48:49)
[GCC 4.8.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>>

输入相关代码,按回车键执行,最后回到“>>>”提示符中:

>>> print("Hello Sir Newton.")
Hello Sir Newton.
>>>

若想获得帮助,可以随时使用help()函数。而用exit()函数可以回到命令行。这种方式与bash很像,因为Python的交互方式与bash完全相同,都是“读取—运算—输出”循环(read-eval print loop),简称REPL

但Python中并不是只有死板的REPL。IPython(即Interactive Python)提供了更强大的REPL。读者可以通过下面的方式获取IPython。

1.访问ipython.org,下载最新的稳定发布版。

2.如果使用的是Conda包管理器(前言中介绍过),并且运行了前言中“安装和设置”一节中列出的命令,那么应该可以使用IPython了。读者可以运行conda update ipython来确保机器上使用的是最新版本。

3.如果安装了Python,可以使用pip install ipython安装。

4.如果使用的是Ubuntu,运行命令sudo apt-get install ipython。

启动IPython并在其中执行代码,如下所示:

$ ipython
Python 2.7.5+ (default, Sep 19 2013, 13:48:49)
Type "copyright", "credits" or "license" for more information.

IPython 1.1.0 -- An enhanced Interactive Python.
?         -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help      -> Python's own help system.
object?   -> Details about 'object', use 'object??' for extra details.

In [1]: print("Good day, Madam Curie.")
Good day, Madam Curie.

In [2]:

除了基于文本的REPL,IPython还提供了基于Web浏览器的notebook,其使用方式和外观与Mathematica或MATLAB的notebook非常相似。这些notebook都是非常好的数据分析平台,且正在快速成为创建和分享信息的标准方式。强烈建议读者去了解一下。

虽然REPL很有用,但也有诸多缺点。首先是REPL在使用多行代码时比较难、繁琐且容易出错。其次是很难将代码保存到普通文件中。因此很难将在REPL环境中完成的工作分享给他人。

大部分人一般在文本文件中编写Python代码。如果对以.py结尾的文件运行解释器,Python会执行其中所有的代码,就如同在REPL中逐行执行一样。

例如有一个名为greetings.py的文件,含有下面的内容:

print("Hey Isaac, what's Newton?!")
print("How is it going, Gottfried?")

在bash中执行会得到下面的结果:

$ python greetings.py
Hey Isaac, what's Newton?!
How is it going, Gottfried?

在学会如何运行Python代码后,下面来学习语言本身。

所有现代编程语言都有注释字符。注释字符会让解释器忽略一部分字符,让开发者能够在相关代码附近撰写一些说明或解释。Python使用“#”字符表示注释内容。解释器会忽略掉#之后位于同一行的所有内容,Python没有多行注释:

# this whole line is a comment
this_part = "is not a comment" # this part is a comment

变量由两部分组成:名称和值。若想为一个名称给定一个值,需要用到单个等号(=)。将变量的名称放在等号的左边,值放在右边。变量的名字可由大小写字母、数字(0~9)、下划线(_)组成。下面是h_bar变量,表示约化普朗克常数(reduced Planck constant,即狄拉克常数):

h_bar = 1.05457e-34

变量名称不能以数字开头,此举是为了防止用户重新定义数值字面值。变量必须以字母或下划线开头。

 

记住,变量名称不能以数字开头!

2plus_forty = 42 # bad

two_plus40 = 42 # good

定义完变量后,就可以随意使用或操作这个变量了。假设需要打印普郎克常数。首先定义π,接着用h_bar乘以2π:

pi = 3.14159
h = 2 * pi * h_bar
print(h)

在Python中,所有变量都有确定的类型,这意味着这些值具有明确定义的属性,用于指定其使用方式。各种类型有针对特定用途的专门属性。整数(int)和浮点数(float)用于数学计算;字符串(str)用于文本处理。这些都是字面类型,因为Python提供了专门的语法用于直接创建这些类型:

dims = 3                  # int, only digits
ndim = 3.0                # float, because of the '.'
h_bar = 1.05457e-34       # float, because of the '.' or 'e'
label = "Energy (in MeV)" # str, quotes surround the text

整数和字符串有时称为精确类型,因为所有精确类型的变量都能在底层精确表示。整数1就是1,不会是其他内容。但浮点数是不精确类型。

一般来说,浮点数是实数的64位近似值[3]。有些浮点数(如1.0),可以用有限的数据表示,但其他浮点数可能不是精确值。这会让科学计算出现许多问题。更多信息请阅读David Goldberg的《What Every Computer Scientist Should Know About Floating- Point Arithmetic》。

如果不清楚变量,可以使用内置的type()函数查看。使用这个函数时,将需要检查的变量放在括号中。

In [1]: type(h_bar)
Out[1]: float

In [2]: type(42)
Out[2]: int

Pothm中还可以使用类型名称来转换变量类型。首先输入类型名称,在其后跟上括号,括号里面是需要转换的变量。

In [1]: float(42)
Out[1]: 42.0

In [2]: int("28")
Out[2]: 28

在表达式int("28")中,字符串"28"转换成了整数,这是因为该字符串中的内容正好都是数字。如果字符串中的内容不能表示一个整数,则会转换失败,如下所示:

In [1]: int("quark")
ValueError                                Traceback (most recent call last)(1)
<ipython-input-5-df7f23f9b45e> in <module>()(2)
----> 1 int("quark")(3)

ValueError: invalid literal for int() with base 10: 'quark'(4)

(1)这里产生一个错误(ValueError)。

(2)错误的位置(指出是在文件中还是在交互式解释器中)。

(3)发生错误位置的行号,以及偏移量。

(4)最后是最重要的错误消息。请仔细阅读这条信息。如果错误信息不清晰或读不懂,可以在网上以错误信息作为关键字进行搜索。

这是Python标准的处理模式,能激发开发者的探索和创造能力。如果某个操作无法进行下去,那么代码应尽早报错并返回有用的错误消息。这种“早撞墙早回头”的信条是交互式开发过程的核心,能鼓励程序员不停地试验,针对错误调整代码再次尝试,重复这个过程直到代码收敛为工作版本。在前面的示例中,“quark”是个字符串,不是基数为10的数值。为了修改这个错误,最好将值更改为仅由数字组成的字符串。

Python是动态类型的,这意味着以下几点。

1.类型是关联到变量的值上,而不是关联到变量的名称上。

2.在使用某个变量之前,不需要知道该变量的类型。

3.改变变量的值后,变量名称的类型就随之改变。

下面的代码在Python中完全正确:

x = 3
x = 1.05457e-34
x = "Energy (in MeV)"

在这里,每次在向x赋予新值时,都改变了其类型。新值替换了旧值,但变量名称没有改变。这与静态类型语言,如C、C++、Fortran、Java都有所区别,在那些语言中:

1.类型关联到变量的名称上,而不是变量的值。

2.在使用变量之前必须指定其类型(通过声明或推导)。

3.无法改变变量类型,即使改变变量的值也不行。

本书不会对静态语言着墨过多,但重要的是要知道,Python中的许多语言特性是为了减少在使用低阶语言时会遇到的不便之处。变量的类型就是这样一个例子,Python对低阶语言中一些严格的限制进行了抽象。而Python的这种灵活性也伴随了一些折衷,在后续遇到时会一一介绍。

Python有一些重要的特殊变量,这些变量的值已经内置到语言中,如True、False、None、NotImplemented。这些变量在启动Python解释器时就存在,且只初始化一次,因此都是单例模式。现在来深入了解这些特殊变量。

布尔类型只含有True和False这两个变量,在Python中类型为bool。布尔变量用来表示Python表达式的真假值。开发者也可以直接作为标志使用,表示开启或关闭某个行为。其他数据类型也可以转换为布尔值。一般来说,如果值是0或容器为空,转换为False;如果值非0或容器非空,则转换为True。下面是仅有的两个转换规则。

In [1]: bool(0)
Out[1]: False

In [2]: bool("Do we need Oxygen?")
Out[2]: True

None在Python中是一个特殊的变量,用来表示没有给定值,或没有定义行为。这与使用0、空字符串或其他任何空值都不同。0是一个合法的数字,而None不是。如果在一个程序中需要一个整数或浮点数,但传进去的是None则程序会终止。如果传入的是0,程序会继续运行。None就如同C/C++的NULL、JavaScript的null一样。另外,None在Python中还有特殊的地位,用来表示函数默认的返回值,后续章节会介绍更多相关内容。

与None不同,变量NotImplemented不仅用来表示某个行为尚未定义,同时也表示该行为在Python中不可能完成、没有意义或不存在。例如,当试图用字符串除以浮点数时就会在底层遇到NotImplemented,导致TypeError:

In [1]: "Gorgus" / 2.718
TypeError Traceback (most recent call last)
<ipython-input-1-8cdca6dc67bb> in <module>()
----> 1 "Gorgus" / 2.718

TypeError: unsupported operand type(s) for /: 'str' and 'float'

NotImplemented对自定义类型非常重要,在第6章会深入介绍。

学习完类型、变量、Python的特殊变量之后,接下来介绍能对变量做些什么。下一节介绍一些Python语言定义的行为和操作。

操作符是一种Python语法,用于表示一些常见的处理数据和变量的方法。Python定义了一元(unary)、二元(binary)和三元(ternary)这三种操作符,分别对应以1个、2个、3个变量作为参数的操作符。

表2-1显示了计算物理中需要了解的操作符。当然,每个操作符的重要程度并不相同。这里先介绍几个重要的,其他的会在用到的时候介绍。注意,有些操作符只能用于特定类型或变量。

表2-1 Python操作符(以变量x、y、z说明)

名  称 使 用 方 式 返 回 值
一元操作符
正号 +x 用于数值类型,返回x
负号 -x 用于数值类型,返回-x
否定 not x 逻辑取反,True转换成False,反之亦然
位取反 ~x 将x的二进制形式按位取反
删除 del x 删除变量x
调用 x() 返回调用x的结果
断言 assert x 检查bool(x)的值是否为True
二元操作符
赋值 x = y 将名称x的值设为y
访问属性 x.y 获取x中的值y
删除属性 del x.y 从x中移除y
索引 x[y] 获取x中位于y位置的值
删除索引 del x[y] 删除x中位于y位置的值
逻辑与 x and y 如果bool(x)和bool(y)都为True则返回True,反之返回False
逻辑或 x or y 如果bool(x)为True,则结果为x,反之为y
二元算术运算符
加法 x + y
减法 x - y
乘法 x * y
除法 x / y 商,在Python 2中只含有整数部分,在Python 3中还包括小数部分
地板除 x // y 求除法中的商
取模 x % y 求除法中的余数
指数 x ** y x的y次方
按位与 x & y 逐位比较x和y的二进制形式,两者都为1,则该位为1,否则该位为0
按位或 x | y 逐位比较x和y的二进制形式,只要其中有一个是1,则该位为1,否则该位为0
按位异或 x ^ y 逐位比较x和y的二进制形式,其中有且只一个是1,则该位为1,否则该位为0
向左位移 x << y 将x的二进制形式向左移动y位。对于整数相当于x乘以2y
向右位移 x >> y 将x的二进制形式向右移动y位。对于整数相当于x除以2y
原地操作 x op= y op可以相当于上面所有的操作,op=相当于原地操作,意味着直接在x上进行操作,结果也直接应用到x上。例如x += 1是向x加1
二元比较运算符
等于 x == y True或False
不等于 x != y True或False
小于 x < y True或False
小于等于 x <= y True或False
大于 x > y True或False
大于等于 x >= y True或False
包含 x in y 如果x是y中的一个元素则为True,反之为False
不包含 x not in y 如果x是y中的一个元素则为False,反之为True
等价性测试 x is y 如果x和y指向同一个底层值则为True,反之为False
非等价测试 x is not y 如果x和y指向同一个底层值则为False,反之为True
三元运算符
三元赋值 x = y = z 将x和y的值设为z
属性赋值 x.y = z 将x.y的值设为z
索引赋值 x[y] = z 将x中y位置的值设为z
三元比较 x < y < z True或False,等价于(x < y) and (y < z),这里的<可以替换为>、<=、>=
三元或 x if y else z 如果bool(y)为True,则结果为x,反之为z。等价于C/C++中的y?x:z

表2-1中列出的大多数操作符都可以互相组合。也就是说操作符之间可以互相连接、嵌套、用括号规定运算顺序。这与组合数学运算符完全相同,如下所示:

 (x < 1) or ((h + y - f) << (m // 8) if y and z**2 else 42)

但有些特定类型的操作符(如赋值(=)和删除(del)操作符)不能组合,必须位于单独一行。因为这些操作符会直接修改变量本身,而不是修改变量的值。如下所示:

x = 1 # Create x
del x # Destroy x

如果某个操作符是可组合的,则可以用其组成Python表达式。表达式是一段代码,执行时并不需要独占一行。同一行代码可以有多个表达式。另一方面,如果某个操作符不可组合,且需要独占一行才能工作,则称为语句。从本质上说,所有的Python代码都由一系列语句组成,而语句由表达式组成。来看下面这个例子。

x = (42 * 65) – 1

这行代码由x = <code>赋值语句组成,等号的右边是表达式(42 * 65) – 1。该表达式由两个子表达式组成,分别是(42 * 65)和<code> –1。这两个子表达式也可以独立执行,如下所示:

In [1]: 42 + 65
Out[1]: 107

In [2]: (42 + 65) + 1
Out[2]: 108

下一节将介绍一个与之前见到的数值类型有所不同的新数据类型。字符串用来表示各种类型的文本,这是编程中的一个核心内容。

字符串是Python中基础数据类型之一,类型名称是str。str也可以用于将其他类型转换成字符串。例如str(42)会返回"42"。定义字符串字面值最简单的方法是使用两个单引号或两个双引号:

x = "Nature abhors a vacuum"
y = 'but loves a mop!'

将字符串视为一串单独的字符虽然很直观,但并不正确。从历史上,计算机中的确曾经用这种方式表示过字符串,且一般来说这仍然是正确的思维模型。那么现在在文本处理方面到底发生了哪些变化呢?

Python没有其他语言中char这样的字符类型。C语言中的char类型由8位组成(1字节)。总共有256(28)个组合,对应ASCII扩展字符集。网上可以找到完整的ASCII表。举个例子,ASCII表中65~90表示的是大写字符A~Z。这些字节依次排列起来就能组成字符串,进而构成了人们可读的文本。对于英语用户来说,这就足够了。

在历史长河中,人们为形形色色的语言创造了大量的字符,远远不止256个。从开发者的角度来看,在表示不同的自然语言中的字符串时最好使用同一种数据类型。在20世纪80年代后期,开发者尝试用“数字到字符的映射”这种构想来表示所有的字符。这就是后来的Unicode。

Unicode目前支持多达11万个字符。为了表示这么多字符,每个字符占用的空间就不止8位。不同的Unicode编码中用到的字节数也不相同,从1到4不等。不同编码中,每个字节的含义也不同。

Python 3没有像Python 2那样继续使用ASCII扩展字符集实现字符串类型,而是使用了Unicode。因此,在 Python 2和早期的概念中,字符串是字符数组。而在Python 3中,字符串是字节数组,并有对应的编码。为了应对不同的情况,Python的字符串稍微有些复杂,但仍然易于使用。

对于大多数科学计算任务来说,基础类型都是数值类型。与Web开发者不同,科学计算方面更关注比较微妙的浮点数,而不是错综复杂的Unicode。因此在字符串方面一般不会遇到严重问题,默认的字符串行为一般就足够了。如果还遇到问题,就使用UTF-8编码。

 

Python 2在科学计算中仍然很流行。其中Unicode是一个单独的类型unicode,而str类型仍然是ASCII。不过这个界限很模糊,因此会导致许多问题。如果想在Python 2中确保使用的是Unicode,那么请在字符串前面加上u,如u"this bytes"。在Python 3中,解释器会忽略掉前导的u,因此这个表达式也能兼容Python 3。

对字符串索引可以获取字符串中部分或全部数据。Python中所有序列型数据都能使用索引,方法是在变量后使用方括号([])。

 

后续章节会不断用到这里介绍的索引用法。

对字符串索引最简单的方式是在方括号中写一个数字,放在字符串的后面。Python索引从0起算。意味着元素计数是从0开始,然后1、2、3这样。因此,若要获取字符串中第二个元素,需要使用索引1。读者若想亲自尝试,从命令行使用ipython命令打开IPython,在In中输入下面的内容。然后按回车,可以在Out行看到结果:

In [1]: p = "proton"
In [2]: p[1]
Out[2]: 'r'

 

如果读者一开始认为从0开始有点奇怪,不要担心,很快就能适应。许多其他语言中(如C/C++)也是从0起算。MATLAB和其他一些语言是从1起算。还有一些语言,如Fortran,可以从任意一个数字起算。这意味着开发者可以声明一个整数,然后赋值给第一个元素,此后所有其他索引都会从这个整数开始。

字符串元素也可以用负数获取。正数是从前计数,而负数是从末尾计数。最后一个元素的索引是–1,倒数第二个是–2,依此类推。负数索引可以看作是用字符串长度减去一段字符数。下面是使用负数索引的示例和对比:

In [3]: p[-1]
Out[3]: 'n'

In [4]: p[len(p)-2] # also works, but why write len(p) all the time?
Out[4]: 'o'

如果想一次性获取多个元素,如提取一个子串,可以使用切片当索引。切片与序列本身无关,只是表示一个索引范围。最简单的切片是用分号隔开的两个整数:s[start:stop]。在IPython继续测试下面的代码:

In [5]: p[2:5]
Out[5]: 'oto'

注意,原字符串末尾的字母n(p[5]所在的地方)没有出现在子串中。这是因为切片的定义就是含头不含尾。切片的定义类似数学中的[start, stop)。

从切片中就能明白为什么Python是从0起算的。从0起算的话,stop和start的差值一定是子串的长度。换句话说,对于任意序列s,下面的代码总为真:

(stop - start) == len(s[start:stop])

如果将索引想象成是元素两边的位置,而不是元素本身的位置,理解上会简单一点。切片从start遍历到stop,收集遍历途中遇到的元素,如图2-1所示。

图2-1 索引不是元素所在的位置,而是在元素的两边

在使用切片时,可以随意混合使用正索引和负索引。但切片在越过序列的左右边缘时不会折返。如果索引试图越过边缘,就会获得一个空序列,因为在这两者之前没有元素。

In [6]: p[1:-1]
Out[6]: 'roto'

In [7]: p[-1:2]
Out[7]: ''

切片有个非常重要的特性,即start和stop值都可以省去。如果省去其中任何一个值,运行时会替换为一个合理的默认值。一般来说,start会替换为0,而stop替换为序列的长度。但省略start和stop时,分号依然要保留,表示这是个切片而不是一个整数索引,如下所示:

s[:2]  # the first two elements
s[-5:] # the last five elements
s[:]   # the whole string!

切片还有一个参数:step(步长)。step表示的是切片中每一次沿着序列前进的元素个数。有时也称为stride(步幅)。step用于间隔获取元素,比如每隔三个元素获取一个元素。step默认值为1,表示获取序列中的每个元素,不跳过任何元素。步长的语法与起点、终点的表示方法非常相似,仅仅是在stop值后面添加一个冒号和一个整数。因此,切片完整的表示方法是s[strat:stop:step]。与start和stop值一样,step也可以为负值,表示从后向前处理。下面是一些使用步长的例子:

In [1]: q = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz"

In [2]: q[2:-2:2]
Out[2]: 'BCDEFGHIJKLMNOPQRSTUVWXY'

In [3]: q[1::2]
Out[3]: 'abcdefghijklmnopqrstuvwxyz'

In [4]: q[::-3]
Out[4]: 'zYwVtSqPnMkJhGeDbA'

切片简单易用,因此在Python代码中很常见。比如逆序一个序列最简单的方式是使用步长为-1的切片:s[::-1]。这样就能非常方便地测试回文。

In [1]: x = "neveroddoreven"

In [2]: x == x[::-1]
Out[2]: True

切片是一种类型,在进行索引操作时单独创建其实例。切片可以存储并多次使用。若要单独创建切片,需要用到表达式slice(start, stop, step)。若想使用默认值,可用None替代整数索引来作为参数:

In [3]: my_slice = slice(3, 1415, 9) # my slice of the pi

In [4]: x[my_slice]
Out[4]: 'ee'

前面介绍的关于索引和切片的规则非常重要。因为这些内容一般能应用到所有Python序列类型中。字符串就是一种最基本的序列,第3章和第9章会介绍更多的序列。

许多操作符都可以应用到字符串上。首先来看看加法操作符(+),在这里用于连接字符串,将两个字符串粘合成一个更大的字符串:

In [1]: "kilo" + "meter"
Out[1]: 'kilometer'

其他数据类型与字符串连接时,先要将其转换成字符串。看下面与数值类型连接的例子:

In [1]: "x^" + str(2)
Out[1]: 'x^2'

在加法的基础上,乘法(*)表示多次相加。将字符串乘以一个整数会生成多个字符串的副本,然后连接在一起。

In [1]: "newto" * 10
Out[1]: 'newtonewtonewtonewtonewtonewtonewtonewtonewtonewto'

数学操作符中只有加法和乘法能用于字符串,字符串不能进行减法、除法、指数运算。但在格式化字符串的时候可以使用取模操作符(%)。取模与这一节的主题无关,也不建议读者使用这个操作符(因为%是C语言中的使用习惯,Python中建议使用format函数)。

到现在已经介绍了几种创建字符串的基本方法,下面再介绍其他几种方法。首先,两个连续的字符串的字面值会自动连接在一起:

In [1]: "H + H" " -> H2"
Out[1]: 'H + H -> H2'

Python会忽略掉括号内部的换行符,因此长字符串可以拆开放在不同行中:

quote = ("Science is what we understand well enough to explain to a computer. "
         "Art is everything else we do. "
         "-Donald Knuth")

如果字符串本身就含有单引号或双引号其中之一,那么最外层就用另一种:

x = "It's easy!"
y = 'The computer said, "Does not compute."'

这种方式需要字符串中仅含有一种引号。若单引号双引号都有,则需要使用反斜杠()对字符串内部的引号进行转义:

"Bones said, \"He\'s dead, Jim.\""

除了引号,还存在其他特殊字符。所有的转义字符都以反斜杠开头,虽然转义字符加特殊字符为两个字符,但只解释为一个字符。表2-2列出了一些重要的特殊字符。

表2-2 字符串转义字符

字  符

解  释

\

反斜杠

\n

换行符,开始一个新行

\r

回车符,转到下一行起始处

\t

Tab

\'

单引号

\"

双引号

字符串字面值还可以添加一个字符的前缀,用来改变字符串的解释方式。表2-3列出了这些前缀。

表2-3 字符串前缀

前  缀

示  例

含  义

r

r"escape!\n"

原始字符串,其中的反斜杠都会自动转义。在这个例子中,\n是\和n,不是换行符。

b

b"this bytes"

字节数组,不是字符串数组,引号内部的内容以字节序列进行解释。

u

u"René Descartes"

Unicode字符串,字符串显式解释为Unicode类型。用在Python 2中,Python 3会忽略掉这个前缀。

最后,Python还支持多行字符串,此时会保留内部的换行符。多行字符串使用三个单引号或三个双引号创建,建议读者使用三个双引号。多行字符串字面值原本是用于代码文档,后面会介绍。在下面的例子中,注意"""只出现在字符的开头和末尾,中间虽然有换行符,但已经不再需要额外的引号了:

"""Humpty, he sat on a wall,
Then Humpty, he had a great fall.
But all the king's horses
And men with their forces
Couldn't render his entropy small.
"""

学完了创建字符串,下面就介绍如何处理字符串。下一节将介绍一些只能用于字符串的操作。

在Python中,变量内部还能含有其他变量。这些内部变量称为属性。属性简称attrs,使用点号操作符访问。假设x中有y,那么表达式x.y意为“到x找到其内部的y”。字符串也是如此。

另外,有些属性是函数类型,称为方法,第五章和第六章会详细介绍方法。现在,只要知道能用于处理字符串的特殊操作都是方法。使用时用括号操作符——()调用这些方法。有些情况下需要在括号中添加额外的参数。

 

下面并没有完整介绍所有的字符串方法,只涵盖了对科学计算最有用的一些方法。

strip()方法用于归一化基于文本的数据。该方法会移除字符串中开头和末尾的所有空白字符,但保留中间的空白字符。空白字符包括空格、Tab、换行和其他空字符。假设有个纯文本的数据文件,但标题中有一些奇怪的空白字符。为了移除标题开头和结尾的空白字符,可以启动IPython,输入标题的字符串,然后对其调用strip():

In [1]: header = " temperature pressure\t value \n"

In [2]: header.strip()
Out[2]: 'temperature pressure\t value'

这里首先将标题定义为普通的字符串。接着输入header.strip到header中查找strip。接着添加括号来调用这个方法,来获得处理后的字符串。

upper()和lower()方法会返回新的字符串,分别将原字符串中的字符全部转成大写或小写:

In [3]: header.upper()
Out[3]: ' TEMPERATURE PRESSURE\t VALUE \n'

swapcase()方法对字符串中的每个字符转换大小写。

如果字符串中只含有数字,那么isdigit()方法会返回True,反之,则返回False:

In [1]: "10".isdigit()
Out[1]: True

In [2]: "10.10".isdigit()
Out[2]: False

最后,format()函数用于从模板中利用相关参数创建新的字符串。字符串格式化使用的是一种特殊的微型语言,这里不做细说,Python字符串文档中含有详细介绍。基本模板是在尖括号中包含数字,其中的整数索引表示的是后面括号中对应位置的值。如下所示:

In [1]: "{0} gets into work & then his {1} begins!".format("Hilbert", "commute")
Out[1]: 'Hilbert gets into work & then his commute begins!'

字符串格式化能够在不进行类型转换和连接的情况下将数据转换成字符串。下面的两个表达式是等价的,第一个用了format(),比第二个使用加号的要短:

In [1]: x = 42

In [2]: y = 65.0

In [3]: "x={0} y={1}".format(x, y)
Out[3]: 'x=42 y=65.0'

In [4]: "x=" + str(x) + " y=" + str(y)
Out[4]: 'x=42 y=65.0'

这一部分涵盖了目前需要用到的大部分字符串操作。在物理软件中处理字符串的场景相对较少。可能物理学家为其他物理软件生成输入数据,并解析其输出时会大量用到字符串的相关方法。这种情况下大部分字符串表示的都是一些数值。下一节将介绍如何访问当前Python文件或解释器之外的代码。

Python代码一般位于以.py结尾的文件中。当Python解释器加载这些文件后,就称为模块。模块是Python文件中的代码在内存中的表现形式。将多个模块组织在一个目录中,就形成了包(package)。值得一提的是,使用其他语言也能为Python编写模块,称之为扩展模块,一般用C语言编写。

通过模块可以使用相同的方式访问多个代码文件。模块还可用保存代码,并分享给其他用户。Python标准库本身就含有许多模块,分别用于处理不同的任务。这些预置的标准库让Python能够应对不同的场景。模块的用法与Python内置函数完全相同。

不同模块中的代码可能有所区别,但都能用import关键字导入,接着就能访问模块中的变量了。模块本身也能使用其他模块。

import语句有4种形式。第一种是在import关键字后面跟上模块名称,没有.py后缀:

import <module>

导入模块之后,就可以通过属性访问操作符(.)获取其中的变量。这与访问对象的方法完全相同。例如,假设有个文件constatns.py,其中存储了普朗克常量和pi。另一个模块就能到导入constants来计算h_bar:

constants.py

pi = 3.14159
h = 6.62606957e-34

physics.py

import constants
two_pi = 2 * constants.pi
h_bar = constants.h / two_pi

如果变量的使用次数很多的话,每次都输入完整的constants.<var>就有点繁琐。Python中还有from-import语法,用来从模块中导入特定的变量。该语句既可以导入单个变量,也可以使用逗号以分割变量的方式同时导入多个变量:

from <module> import <var>
from <module> import <var1>, <var2>, ...

这种方式等价于导入模块,接着用一个局部变量指向模块中的同名变量,最后删除模块名称,只保留局部变量:

import <module>
<var> = <module>.<var>
del <module>

因此可以将from-import语句理解成一种通过重命名来简化代码的方式。此时就能像下面这样导入constants:

constants.py

pi = 3.14159
h = 6.62606957e-34

physics.py

from constants import pi, h

two_pi = 2 * pi
h_bar = h / two_pi

第三种导入方式会改变导入模块的名称。这种方式适用于当本地变量与模块中的变量有冲突的情形。(一般来说,读者可以随意给变量命名,但最好不要与其他模块中的变量重名)此时需要用到as关键字,语法如下所示:

import <module> as <name>

这等价于先导入模块,接着另起一个新名字,最后将原来的名称删除:

import <module>
<name> = <module>
del <module>

在constatns例子中,如果有个局部变量也为constants,那么只有当重命名constants模块后才能使用其中的pi和h。下面是使用这种语法的示例:

constants.py

pi = 3.14159
h = 6.62606957e-34

evenmorephysics.py

import constants as c

constants = 2.71828

two_pi = 2 * c.pi
h_bar = c.h / 2 / c.pi

在evenmorephysics.py中,constants是欧拉数(即e),而constants.py模块则重命名为变量c。

最后一种形式是将from-import和import-as结合起来,用来导入模块中特定的变量并重命名。在下面的语法中,第一个是导入并重命名单个变量,第二个是导入用逗号分割的多个变量。

from <module> import <var> as <name>
from <module> import <var1> as <name1>, <var2> as <name2>, ...

这种形式等价于从模块中导入一个变量,使用局部变量重命名,最后删除原来的变量名称:

from <module> import <var>
<name> = <var>
del <var>

下面的例子导入并重命名了constants.py模块中的pi和h变量:

constants.py

pi = 3.14159
h = 6.62606957e-34

yetmorephysics.py

from constants import pi as PI, h as H

two_pi = 2 * PI
h_bar = H / two_pi

前面提到过,包指的是同一个目录下的多个模块。若要在Python中使用包,则相关目录下必须含有名为__init__.py的特殊文件。该文件的作用是告诉Python这个目录是一个包,当中除__init__.py之外,其他以.py结尾的文件都是可导入的。__init__.py文件无需含有任何代码,不过如果其中有代码,则在导入时会先执行这些代码,然后再导入包中的其他模块。

包的名称就是目录的名称,其中可能含有的子目录为子包。例如,compphys包的文件系统可能如下所示:

compphys/
|-- __init__.py
|-- constants.py
|-- physics.py
|-- more/
| |-- __init__.py
| |-- morephysics.py
| |-- evenmorephysics.py
| |-- yetmorephysics.py
|-- raw/
| |-- data.txt
| |-- matrix.txt
| |-- orphan.py

在这里,compphys是包名。这个包含有3个模块(__init__.py、constants.py、physics.py)和1个子包(more)。raw目录不是子包,因为其中没有__init__.py文件。即使其中含有Python文件(如orphan.py),Python解释器也察觉不到。

使用属性访问操作符(.)能从包中导入模块。这与从模块中导入变量的语法完全相同。包目录中Python文件的组织结构决定了包中含有的子包名和模块名。如果从一个包中导入子包或模块,就会自动导入子包的上一层的所有内容。但这些自动导入的包是无法访问的,必须再次显式导入才能使用。导入模块后就可以通过点操作符访问其中所有的变量。如下所示:

import compphys.constants(1)
import compphys.more.evenmorephysics(2)

two_pi = 2 * compphys.constants.pi(3)

(1)导入位于compphys包中的constants模块。

(2)导入位于compphys包中的more子包中的evenmorephysics模块。

(3)使用属性访问操作符——点访问compphys包中constants模块中的pi变量。

上面这些需要用到模块的全路径,因此称为绝对导入。

 

相比其他形式的导入,本书建议使用绝对导入。因为绝对导入能够清楚地显示出模块的路径。

在一个包中,不使用包名也能导入同级的其他模块,这称为隐式相对导入。例如在evenmorephysics.py中可以在不使用compphys.more前缀的情况下导入morephysics.py。如下所示:

import morephysics

或在physcis.py中只使用子包名称导入子包中的模块:

import more.yetmorephysics

但在Python中,隐式相对导入已经末路。

 

Python 3移除了隐式相对导入,只能在Python 2中使用隐式相对导入。但本书不建议读者使用。

显式相对导入替代了隐式相对导入。显式相对导入必须使用from关键字,模块名称前面必须有单个点(.)或双点(..)。单点表示当前包;双点表示文件系统中当前包的上一层。与bash中的含义相同。

例如,在physics.py中,下面是合法的导入:

from . import constants
from .constants import pi, h
from .more import morephysics

在evenmorephysics.py中,下面的导入也是正确的:

from . import morephysics
from .. import constants
from ..constants import pi, h

在模块名称前面不能使用两个以上的点,即无法在子包中一次性向上回溯多个层次。如果读者想这么做,最好重新安排代码文件的布局。

Python让用户能够编写完成特定任务的模块和包。但也不是所有东西都要从头写起。Python语言本身也提供了许多用于不同情形的工具,下一节会介绍。

Python之所以能称得上是一个宝贵的工具,其中一个方面是Python自带了非常全面的标准库。标准库是一系列的包和模块,用来以Pythonic的方式快速处理日常任务。包括各种平台独立的操作系统任务、数学函数、压缩算法、数据库和简单的Web服务器等。只要有了Python,就会拥有这些标准工具。表2-4介绍了一些标准库中最有用的一些Python模块。注意,这里只列出了Python标准库中的部分内容。

Python另一个杰出的方面在于其含有完整的生态系统,Python含有丰富的第三方库。虽然第三方库不算标准库,但无论从广度和深度上都远远超过标准库。其中许多库非常适合用于科学和物理方面的计算。本书许多其他章节都会用到一些非常棒的第三方库。

表2-4 Python标准库中重要且有用的模块

模  块

描  述

os

操作系统的抽象层,用于处理文件路径、删除文件等工作

sys

系统相关,深入Python解释器内部

math

常见的数学函数和常量

re

正则表达式库,参见第8章

subprocess

生成并执行子进程和shell,用于运行其他命令行工具

argparse

用于创建简单、美观的命令行工具

itertools

用于循环的有用工具

collections

高级集合类型和工具,用于创建自定义集合

decimal

任意精度的整数和浮点数

random

伪随机数生成器

csv

用于读取和写入CSV文件的工具

pdb

Python调试器(类似C/C++/Fortran的gdb)

logging

对运行的程序进程记录日志的工具

到了这里,读者应该掌握了下面的内容:

有了这些基础,读者就可以开始构建科学计算软件中更加复杂的数据和逻辑。第3章将介绍如何使用Python一些内置的机制来收集数据。

[1] 原文是is bad,并不是高德纳的is the root of all evil,但考虑到后者更流行,且意思相差不大,所以使用后面的中文意思。

[2] 若想了解各语言之间的详细对比,请参考Lutz Prechelt的文章《An Empirical Comparison of Seven Programming Languages》。

[3] 从数学角度来说,浮点数集合不是实数的子域,也不是实数扩展集的子域。事实上,浮点数根本就不是一个域!由于浮点数包含元素NaN(即Not a Number),而NaN无法取反,因此浮点数算不上是一个域。


相关图书

深度学习的数学——使用Python语言
深度学习的数学——使用Python语言
动手学自然语言处理
动手学自然语言处理
Web应用安全
Web应用安全
Python高性能编程(第2版)
Python高性能编程(第2版)
图像处理与计算机视觉实践——基于OpenCV和Python
图像处理与计算机视觉实践——基于OpenCV和Python
Python数据科学实战
Python数据科学实战

相关文章

相关课程