对 DNA3 进行的赋值是一个典型的赋值操作,就像你在例 4.1看到的那样,变量名后面跟着 = 符号,= 后则是要被赋予的值。
赋值语句中右边的值是包裹在双引号中的字符串。双引号会把字符串中的%s替换为变量的值,这叫做字符串格式化。所以事实上,此处的字符串就是 DNA1 变量中的 DNA,后面紧跟着 DNA2 变量中的 DNA。两个 DNA 片段连接后就被赋值给了变量 DNA3。
在把连接结果赋值给 DNA3 变量后,你把它打印出来,后面跟着一个空行:
程序的下面一部分就演示了连接两个字符串的另外一种办法——使用加法操作符。当把加法操作符放在两个字符串中间时,它会把原来的两个字符串连接起来,产生一个新的字符串。所以这一行,演示了加法操作符的使用。
最后,来练习一下 Python 的另外一种方法,仅仅使用 print 语句就可以完成同样的连接任务:
此处的 print函数语句有用逗号分隔开的四部分:存储在两个变量中的两个 DNA 片段、一个换行符和连接符参数。
在结束这一小节之前,让我们来看看 Python变量的其他用法吧。你已经看到,使用变量可以存储 DNA 序列数据的字符串。还有其他类型的数据,编程语言也需要变量来存储它们。在 Python 中,一个像 DNA 这样的标量变量可以存储字符串、整数、浮点数(有小数点的数字)、布尔值(True 或 False)等。现在,试着在例 4.1或例 4.2中添加如下几行,在标量变量中存储一个数字并把它打印出来:
作为生物信息学中的 Python 程序员,你很大一部分时间都是在做类似于例 4.1和例 4.2那样的事情:你获取一些数据,可能是 DNA、蛋白质、GenBank 条目或者其他数据,处理这些数据,并把处理结果输出打印出来。例 4.3是处理 DNA 的另一个程序:它把 DNA 转录成 RNA。在细胞中,把 DNA 转录成 RNA 是由精巧、复杂且有勘误功能的分子机器完成的。此处仅是简单的替换而已。当 DNA 转录成 RNA 时,所有的 T 都会被替换成 U,这也正是我们的程序所要做的全部工作。
例 4.3:把 DNA 转录成 RNA
这是例 4.3的输出:
这个简短的程序展示了 Python 重要的特性:轻松处理 DNA 字符串等文本数据的能力。
类似的处理可能会有多种:翻译、反转、替换、删除和重排序等等。总的来说,Python 在此类任务中的便利是其能够在生物信息学领域成功及在程序员中广泛应用的主要原因。
首先,程序生成了一个 DNA 的拷⻉,并把它存储在叫做 RNA 的变量中:
注意在执行这个语句后,叫做 RNA 的这个变量存储的就是 DNA。你可以给变量起任何你喜欢的名字,这是完全合法的,但不准确的变量名可能会导致一些混乱。在这个例子中,拷⻉完变量值后,紧跟着的是内容丰富的注释,之后便是让 RNA 变量真正包含 RNA 的语句,所以此处给它起名为 RNA 完全没有问题。有一个让 RNA 只包行 RNA 而不包含其他内容的方法:
在例 4.3中,转录过程发生在这个语句中:
在这个语句中有两个新的项目:赋值操作符(=)和替换函数 replace。很明显,赋值操作符 = 用于包含字符串的变量,如此处的 RNA 变量储存 DNA 序列转录后的RNA序列数据。replace函数就是“把 DNA 变量存储的字符串数据中的所有 T 都替换成 U”。
字符串操作对于文本处理来说至关重要,在后续的章节中你将看到,字符串操作是 Python 最为强大的特性之一。
对于 Python 程序员来说,最重要的资源便是 Python 的文档。它应该已经安装在了你的电脑上,另外通过因特网在 Python 网站上也可以找到它。对于不同的计算机系统来说,Python 文档的格式可能有少许的差别,但网络版对任何一个人来说都是一样的,这也正式我在本书中参考的版本。参看附录 A中的参考资料,你会找到对于 Python 文档不同资源的讨论。
来试一下,让我们找找 print 操作符的文档吧。首先,打开你的网⻚浏览器,进入 https://www.python.org/ 网站,然后点击python3.x文档链接,依次选择“一般索引”(”General Index”)。你会看到一个把 Python 对象按字⺟顺序进行排列的一个冗⻓的列表。一旦你找到这个⻚面,你可能需要把它收藏到浏览器的书签中,因为你会频繁地访问该⻚面。
现在点击 print 来查看 print 函数的文档吧。
看一下文档中的例子,看看 Python 语言的特性是如何被运用的。这往往是你找到所需内容的最快方法。
一旦在你的屏幕中打开了文档,你会发现通过阅读它会找到一些答案,但也会产生其他一些疑问。文档试图以一种简洁的形式把所有的内容都包含进去,但这却会让初学者们心生胆怯。比如,print 函数文档的开头还比较简单:“将对象打印到文本流文件,以sep分隔,然后结束。 sep,end,file和flush(如果存在)必须作为关键字参数给出”。但之后就是一堆的胡言乱语了(在你学习的现阶段它看起来确实是这样):sep?end?file?flush?
文档中的所有信息都是必需的,毕竟,你需要在某个地方找到这样的全部内容!通常情况下,你可以忽略掉那些对你来说毫无意义的内容。
Python 文档中也包含了一些对学习 Python 有很大帮助的教程。有时,它们会假定你掌握了比一个编程语言初学者应当掌握的知识更多的知识,但你会发现它们仍然非常有用。翻阅文档是在学习 Python 语言过程中快速成⻓的绝佳途径。
第 1 章中已经提到了,DNA 聚合物是由核苷酸构成的。考虑到 DNA 双螺旋中两条链的亲密关系,最好能编写这样一个程序:给出一条链,输出另一条链。这样的工作对许多生物信息学应用程序来说都是非常重要的一部分。比如,当在数据库中查询某条 DNA 时,常常也需要自动查询该 DNA 的反向互补序列,因为你手上的序列有可能是某个已知基因的负链。回到正题,这是例 4.4,它使用了一些新的 Python 特性。就像你将看到的那样,它首先尝试一种方法,失败了,然后尝试另外一种方法,最终取得了成功。
例 4.4:计算 DNA 一条链的反向互补链
这是你屏幕上例 4.4的输出:
通过从不同的末端开始读起,也就是说一条链从左向右读,另一条链从右向左读,你可以检查一下 DNA 的两条链是不是反向互补的。当你读这两条链的时候,比较它们对应的碱基,应该总是 C 和 G 对应、A 和 T 对应。
在第一次尝试中,试着从原始的 DNA 和反向互补后的 DNA 中读几个字符,你会发现反向互补的第一次尝试失败了。这是一个错误的算法。
这是在你程序中经常要经历的一种体验。你写一个程序来完成某项任务,但却发现它并没有像你期望的那样工作。在这种情况下,我们需要运用已掌握的知识来尝试解决全新的问题。它并没有如期望那样完成任务,哪里出错了呢?
你会发现这样的经历会非常相似:你编写代码,但它并不工作!然后你就修正语法(这通常是最简单的,而且利用错误信息提供的线索就可以轻松修正语法),或者对问题进行思考,找到问题所在,并尝试设计一种新的可以成功的方法。通常情况下,这需要你浏览语言的文档,查找语言工作过程的细节内容,同时期望能找到一个解决问题的特性。
如果这个问题在计算机上能够解决,那么你用 Python 也可以将它解决。问题是,能够精确到什么程度?
在例 4.4中,计算反向互补的第一次尝试失败了。使用四个全局替换,序列中的每一个碱基都作为一个整体进行了处理。还需要另外的方法。你可以从左向右查阅 DNA,每次只查找一种碱基,把它替换成互补的碱基,然后再在 DNA 中查找另外一种碱基,一直到字符串的结尾。然后把字符串反转过来,任务就完成了。事实上,这是一个非常好的方法,在 Python 中也不难实现。但还需要学习语言的其他内容才行,这将在第 5 章中进行讲解。
然而,在这个例子中,maketrans先构建了ATCG对应的转换关系,然后translate根据转换关系翻译序列。
序列切片也是我们需要的,虽然有点大材小用了。它被设计用来反转元素的顺序,包括字符串,就像在例 4.4中看到的那样。
到现在为止,我们已经编写了处理 DNA 序列数据的程序,现在,我们也将要处理同样重要的蛋白质序列数据。接下来的几个小节将要学习一些新的知识,这里是一个简单的概述:
- 在 Python程序中如何使用蛋白质序列数据
- 如何从一个文件中读入蛋白质序列数据
- Python 语言中的列表在本章的剩余部分中,蛋白质和 DNA 序列数据都将使用到。
程序要和计算机磁盘中的文件进行交互。这些文件可以存储在任何永久性存储介质中 ——硬盘、光盘、软盘、Zip 磁盘、磁带等。
让我们看看如何从文件中读取蛋白质序列数据吧。首先,在你的计算机上创建一个文件(使用文本编辑器),并在其中存储一些蛋白质序列数据。给这个文件起名为
NM_021964fragment.pep(你可以在书籍网站上下载到该文件)。你将使用下列数据(人类锌指蛋白 NM_021964 的一部分):
你可以给它起任意一个名字,只要和文件夹中已有的文件不重名即可。
就像好的变量名对于理解程序至关重要一样,好的文件名和文件夹名也是非常重要的。如果你有一个项目,这个项目会生成大量的文件,那你就需要认真考虑如何对这些文件和文件夹命名和组织了。不管是对于某一个研究人员还是对于一个庞大的多国团队来说,这都是非常现实的问题。花一些功夫来给文件起一个富含信息量的名字是非常重要的。文件名 NM_021964fragment.pep 来源于蛋白质的 GenBank ID。同时,它还表明了这只是数据的一部分,而文件的后缀.pep 则提醒你文件中保存的是肽或蛋白质序列数据。当然,其他的命名方案可能会更加适合于你。不管怎样,关键的一点就是不需要打开文件,仅仅通过文件名就可以对文件保存的数据有所了解。你已经创建或者下载了保存有蛋白质序列数据的文件,那我们就来编写一个程序吧,这个程序从文件中读入蛋白质序列数据并把它保存到变量中。例 4.5是第一次尝试,随着学习我们会逐步对它进行扩展。
例 4.5:从文件中读取蛋白质序列数据
下面是例 4.5的输出:
注意只有文件的第一行被打印输出出来,稍后我会解释为什么会这样。
让我们仔细研究一下例 4.5吧。在把文件名保存到 proteinfilename 变量中后,下面这个语句会打开文件:
打开文件后,你就可以对它进行各种操作了,比如读取、写入、查找、定位到文件的特定位置、清除文件中的所有数据,等等。注意,程序假设保存在 proteinfilename 变量中的文件是存在的,而且可以打开。你将会看到如何来确认这一点,现在你可以进行一下这样的尝试:更改 proteinfilename 变量中文件的名字,这样计算机上就没有叫原来那个名字的文件了,之后运行一下程序。如果文件并不存在,你会看到一些错误信息。
如果你查阅 open 函数的文档,你会看到好多选项,大多数选项都是在打开文件后让你精确指定如何使用文件的。
让我们来看一下 PROTEINFILE 这个数据,它叫做文件句柄。没有必要理解文件句柄真正是什么,它们就是你处理文件时使用的东西。对于文件句柄,不一定必须使用大写字⺟,但这是一个广为接受的惯例。在使用 open 语句对文件句柄赋值后,对文件进行的任何交互操作都将通过使用文件句柄来进行。
使用这个语句,就可以把数据读入到程序中了:
通过调用文件句柄的readline函数,你就可以从程序外部的来源中读入数据了。在这个例子中,我们读入了 NM_021964fragment.pep 文件中的数据,该文件的名字保存在 proteinfilename 变量中,而 open 语句则把它和文件句柄关联了起来。数据现在就保存到了 protein 变量中,之后可以把它打印输出出来。
然而,就像我们前面已经注意到的那样,只有这个多行文件的第一行被打印了出来。这是问什么呢?因为对于读取文件还有一些知识需要学习。有许多方法可以读取整个文件,例 4.6演示了其中的一种。
例 4.6:从文件中读取蛋白质序列数据,第二次尝试
下面是例 4.6的输出:
这个程序中比较有趣的一点就是,它演示了如何成功得从文件中读取数据。每当把数据读取到 protein 这样的变量中后,都会对文件的下一行进行读取。上一次读取到哪儿了,现在需要读取哪一行,程序对这些信息都有所记录。
另一方面,程序的缺陷也是显而易⻅的。对于输入文件的每一个行,都需要编写一行代码来读入,这样太不方便了。但是,在 Python 中有两个特性可以很好的解决这个问题:列表(下一小节进行介绍)和循环(参看第 5 章)。
在计算机语言中,列表是存储多个标量值的变量。这些标量的值可以是数字、字符串,或者,像此处的例子一样,也可以是蛋白质序列数据输入文件中的每一行。让我们看看如何来使用列表吧。例 4.7演示了如何使用列表来把输入文件中的所有行都读入进来。
例 4.7:从文件中读取蛋白质序列数据,第三次尝试
下面是例 4.7的输出:
就像你看到的,这就是文件中的全部数据。我们成功了!使用数组的方便性显而易⻅——只需要一行代码就可以把所有的数据都读入到程序中了。
请注意,Python中的变量可以赋值为任何类型,另外,“*”将多个元素的列表解包为print函数的多个对象,然后print函数打印每个对象。
列表是存储多个标量值的变量。列表中的每一个项目或者元素都是标量值,可以通过在列表中的位置(它的下标或者偏移量)对它进行引用。让我们来看几个列表和列表常用操作的实例吧。我们定义一个叫做 bases 的输出,其中存储着 A、C、G 和 T 四个碱基。现在我们对它应用几个常⻅的列表操作。
这是一个代码片段,它演示了如何初始化列表,以及如何使用下标来访问列表中的单个元素:
这个代码片段将会输出:
你可以像这样把元素一个接一个的输出出来:
它会产生这样的输出:
你也可以输出用空格分隔的元素(注意 print 语句中的sep参数):
它会产生这样的输出:
使用 pop,你可以从列表的末尾拿掉一个元素:
它会产生这样的输出:
使用 pop,你也可以从列表的开头拿掉一个碱基:
它会产生这样的输出:
使用insert,你可以把一个元素添加到列表的开头:
它会产生这样的输出:
使用append,你可以把一个元素添加到列表的末尾:
它会产生这样的输出:
使用reverse函数,反转列表:
它会产生这样的输出: