手把手 | 教你打包Python脚本并用TBtools完成程序界
导语
前述,师弟 Chuhao Li 用 Python 开发了第一个 TBtools CLI Program Wrapper Creator 插件。插件的实用性和制作的精细程度出乎意料。感慨之余,我邀请他给大伙分享一下制作经验(因为我完全不懂 Python,而我知道 Python 是现在绝大多数朋友最喜欢用的语言)。收到推文,发现非常详细,且内容使用。尤为难得,还直接拓展了 TBtools 的一个功能痛点(没有批量化反向互补功能!)。Chuhao 开发的这个插件,直接填补了这个缺憾,同时还做了多线程加速,我用了下,非常方便,非常快!现在插件已经上传到商店,感兴趣的朋友可下载使用。另,欢迎大伙学习这篇博文后,也一起来开发插件,贡献力量,一起减除更多人的生信数据分析负担。
前言
相信很多学习生物信息学的人都会学习python。你可能遇到过这样的情况:
在一个风和日丽的早上,你耗尽洪荒之力,写好了一个“反向互补”命令行工具,然后很激动地把代码发到群里面,分享给实验室其他人用。
小伙伴就会跟你说:这个脚本要怎么用呀?然后,你就会解释道:首先,安装一个python,然后这样打开终端,然后这样输入参数……。
小伙伴说:“嗯,我先试一下”。然后一顿操作,在不知名网站上下载了个“python”安装包,把python安装好,还付送了一堆4XX9小游戏之类的软件。
如无意外,过一会,小伙伴就会跑过来问你,“我输入命令,怎么报错了”?
你仔细地检查,发现如下问题:
- 安装了python2而不是python3
- 那行命令没有在特定工作目录
- 命令的某个单词少了一个字母
- 某两个参数之间没有空格
一顿操作过后,问题解决了,你还很详细地给小伙伴解释了其中的原理。能帮助到实验室的小伙伴,你很开心,回到工位上,准备迎接一天的工作。
然后,噩梦来了。另外一个小伙伴又来问你同样的问题……
这时候,我会告诉你:用pyinstaller打包你的工具,然后用TBtools的CLI Program Wrapper Creator
来快速构建一个图形界面插件。
5分钟把插件打包好,直接把插件扔给小伙伴们,在群里说:“我项目需求,顺手写了个工具,有需要的拿去。” 然后潇洒离去。
下面我将用实际例子给大家介绍,如何操作。
python反向互补脚本
下面的脚本实现了单条序列的反向互补。为了减少代码量,我使用了biopython
来进行fasta序列的读写和操作。接下来,我要把这个工具打包成TBtools插件。
import sys
from Bio import SeqIO
infile = sys.argv[1]
outfile = sys.argv[2]
seq = SeqIO.read(infile, 'fasta')
seq.seq = seq.seq.reverse_complement()
with open(outfile, "w") as f:
SeqIO.write(seq, f, "fasta")
安装pyinstaller
假设你已经安装好了python,进入cmd
或者powershell
,输入以下命令,即可安装pyinstaller
以及脚本所依赖的biopython
:
注意:如果你的python环境中已经安装了很多别的包,那么pyinstaller打包的时候会把一些不必要的包打包进来。这种情况下,建议使用virtualenv来新建一个新的环境(如果你是刚刚才安装的python,那就没必要做这一步了):
py.exe -m pip install virtualenv
py.exe -m virtualenv my_env
.\my_env\Scripts\activate # 激活环境。激活后可以看到命令行左边多了(my_env)。
# 如果激活失败,那就尝试不要用powershell,转用cmd(新建一个cmd窗口或者在powershell中直接输入cmd即可)。
环境激活之后,安装所需的软件。这里除了pyinstaller,还安装了biopython,这是这个脚本中用到的。
py.exe -m pip install pyinstaller biopython
一般来说,安装完后,在cmd或者powershell下运行下面的命令可以打印出帮助信息。
pyinstaller -h
如果不行,那就试试下面的(注意大小写):
py.exe -m PyInstaller -h
编写脚本需要注意的问题
参数传递
可以使用位置参数(参数前不带“-”的)和长参数(参数前带“--”的)。如果只有位置参数,就直接用sys.argv
;如果要用到长参数,推荐使用argparse
。
值得注意的是,当argparse
和multiprocessing
一起使用的时候,需要使用parser.parse_know_args()
,而不能直接使用parser.parse_args()
,否则传递参数的时候会出错。
多线程
使用到多线程时,主程序需要用if __name__ == '__main__':
保护起来。下面一行紧接着freeze_support()
。
调用其他工具
假如我的程序需要调用其他二进制文件,只需要把exe文件放在与脚本同一个目录下即可。TBtools运行脚本的时候,会自动把脚本所在目录添加到环境变量,于是在脚本中可以不需要指定二进制文件所在的路径。
调用外部命令,注意给输入、输入文件路径加引号,防止输入文件路径中有空格的问题。假设输入文件路径为infile = "C:\Use r\input.fasta"
,中间有个空格。那么,如果使用os.system(f'tool_name.exe {infile}')
来调用就会有问题。需要在infile两边加上引号:os.system(f'tool_name.exe "{infile}"')
。
值得注意的是,如果使用的是blastn.exe
等TBtools已经有的二进制文件,则可以直接调用,不必打包到插件当中。
代码修改
下面的代码,在原来的基础上添加了多线程和使用argparse来解析参数。请注意注释部分。
import argparse
from multiprocessing import Pool, freeze_support
from Bio import SeqIO
def rc(seq):
seq.seq = seq.seq.reverse_complement()
return seq
if __name__ == '__main__': # 使用到多线程时,这一行必须加。下面一行紧接着“freeze_support()”
freeze_support()
parser = argparse.ArgumentParser()
parser.description = "多线程反向互补"
parser.add_argument("--threads", default=1, type=int, help="Number of threads")
parser.add_argument("input", help="Input fasta file")
parser.add_argument("output", help="Output fasta file")
args, unparsed = parser.parse_known_args()
# argparse和多线程搭配使用的时候,这里需要使用parse_know_args(),
# 而不能直接使用parse_args(),否则传递参数的时候会出错。
seqs = SeqIO.parse(args.input, 'fasta')
with Pool(processes=args.threads) as pool:
seqs_rc = pool.map(rc, seqs)
pool.close()
pool.join()
with open(args.output, "w") as f:
for seq in seqs_rc:
SeqIO.write(seq, f, "fasta")
打包
具体操作
在py脚本所在目录,按住“shift”键,然后右键点击文件浏览器的空白处,点击“在此处打开命令窗口”,就能进入终端,并自动切换工作目录为当前目录。
运行以下命令,即可打包:
pyinstaller -D .\multiprocess_rc.py
# 如果不行,就用下面的:
# py.exe -m PyInstaller -D .\multiprocess_rc.py
打包完成,可以看到当前目录多了两个文件夹和一个文件:
其中dist
文件夹里面就是我们打包好的工具。里面长这样。里面的multiprocess_rc.exe
就是程序的入口,其他文件是该程序依赖的资源,必须和主程序放在同一个目录。:
命令解析
在这个行命令中,我传递了-D
和.\multiprocess_rc.py
两个参数给pyinstaller
。
-
-D
参数是指要打包成一个文件夹,把所有依赖的dll等文件放在同一个文件夹中。另外一种方式是-F
,指定-F
参数可以把全部文件打包成单个的exe文件。虽然-F
参数打包出来的文件看上去很干净,其实在实际运行的时候还是会把exe文件里面的东西释放到一个临时文件夹中,然后再运行,而且会引入一些额外的bug。所以,我推荐使用-D
参数。 -
.\multiprocess_rc.py
就是脚本的主程序。如果有多个模块,只需要输入主程序即可。打包工具会自动识别主程序中有import到的其他脚本,一起打包进去。
其他实用参数
如果你需要调用其他二进制文件,比如说tool.exe
。你可以把tool.exe
放在和脚本同一目录,然后打包的时候增加以下参数:--add-data "tool.exe;tool.exe"
。这样就可以把tool.exe一起放在打包好的文件夹中了。当然你也可以手动复制过去,不过如果涉及到多次打包,就会麻烦一丢丢。
输出文件
-
dist
目录:里面有打包好的软件。 -
build
目录:打包过程中生成的临时文件,可以删掉。 -
*.spec
文件:打包相关的配置文件。如果重复打包,可以直接把这个文件代替掉脚本名字,传递给pyinstaller,就不用再写那些参数了。
小技巧
常常会遇到需要打包、测试多次的时候。这时候,把打包和测试相关的命令写入到一个.bat
文件中是比较方便的。这样直接双击.bat
文件就可以重新打包,无需重新输入命令。
关于pyinstaller的更多信息,请参考:
测试pyinstaller打包结果
进入dist/multiprocess_rc/
目录,打开cmd,执行以下命令:
.\multiprocess_rc.exe --threads 4 .\input.fasta output.fasta
可以得到输出结果,代表运行成功。检查文件结果,也确实是反向互补了:
input.fasta:
>test
ATCGGGTTCCAA
>test1
ATCGGGTTCCAA
>test2
ATCGGGTTCCAA
>test3
ATCGGGTTCCAA
>test4
ATCGGGTTCCAA
output.fasta:
>test
TTGGAACCCGAT
>test1
TTGGAACCCGAT
>test2
TTGGAACCCGAT
>test3
TTGGAACCCGAT
>test4
TTGGAACCCGAT
打包TBtools插件
在插件商店安装好CLI Program Wrapper Creator
后,从Tbtools的菜单栏Others > Plugin中即可看到。点击进去,参照下方的设置:
配置完成,点击“Refresh”就可以刷新界面。还可以直接在界面中运行程序,非常方便。如果配置到一半要去吃饭,可以按下方的“Save Config”暂时把界面设置保存为文件,下次可以直接用“Load Config”来加载,省去很多时间。
测试完成,点击右下角的“Export Plugin”,即可导出插件。导出的文件实际上是一个zip压缩文件。建议导出的时候不要加任何后缀,这样后面安装插件时,菜单栏上名字就会带有后缀。导出之后,再重新添加“.zip”后缀。
导出后,进入Others > Install Plugin 安装插件,然后就可以使用了。
把序列粘到Input输入框,点击开始,就可以看到下方的反向互补序列了!
这里我忽略了很多UI设计界面使用的细节,详情可以回头看看公众号前几天的推文,有CJ的详细教程。
总结
把经常要做的、重复性的东西简单化、流程化、自动化,或许可以让大家把更多的时间投入到科学问题的研究中。