6

.py import 引用现成的代码 | 阿掖山:一个博客

 2 years ago
source link: https://mountaye.github.io/blog/articles/python-import-script-module-package
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

.py | import 引用现成的代码

Dec 11, 2021

以官网给出的文件结构为例来说明:


sound/                          Top-level package
      __init__.py               Initialize the sound package
      formats/                  Subpackage for file format conversions
              __init__.py
              wavread.py
              wavwrite.py
              aiffread.py
              aiffwrite.py
              auread.py
              auwrite.py
              ...
      effects/                  Subpackage for sound effects
              __init__.py
              echo.py
              surround.py
              reverse.py
              ...
      filters/                  Subpackage for filters
              __init__.py
              equalizer.py
              vocoder.py
              karaoke.py
              ...

使用现成的 python 代码

正常的编程语言教程,教人配置完开发环境之后就应该进入正题,开始讲语法了。但是咱不正常,所以先来谈谈怎么用别人已经写好的代码。其中最简单的,就是可以直接通过包管理程序安装的:


pip install sound

然后想要使用某个文件中的函数,比如假装 wavwrite.py 中有个函数叫 write(),以下写法都是可以的,注意不同 import 方法对应不同的函数调用写法:


import sound
sound.formats.wavwrite.write()

from sound import formats
formats.wavwrite.write() 

from sound.formats import wavwrite
wavwrite.write()

from sound.formats.wavwrite import write
write()

但是,不是所有的 python 代码都可以直接安装,比如一篇论文的研究成果发表之后,处理数据的代码也往往开源,但是这些作者基本上就只是把自己写代码的文件夹公开出来而已,我们把文件夹下载下来,然后直接 import sound, 会报错,提示找不到名为 sound 的库。

python 如何读取代码文件

仔细想想,找不到才是正常的,之前轻轻松松的一句 import sound就解决问题,这才不简单——不同的库往往位于文件系统的不同位置,但我们只要写出他们的名字就行了,不需要指定文件路径。电脑硬盘那么大,找到库却几乎是瞬间完成的。

这是因为 python 并没有搜索整个硬盘。有一个变量,一般名为 PYTHONPATH,其变量值是一个列表,表中成员是含有 python 库文件夹的路径。当我们在命令行输入命令的时候,电脑会:

  • 搜索当前所在的文件夹,也就是在命令行输入 python 时终端所在的文件夹。
  • 遍历 PYTHONPATH 中的文件夹。
  • python 包管理程序默认的位置,一般是 <path to python>/site-package

看看有没有我们要引用的库,找到了就引入,找不到就报错。

上一节的错误中,如果我们恰好位于 sound 所在的文件夹,然后运行 python,此时第一条生效, import sound 不会报错,但在其他位置就不行了。

名词解释:interactive, script, module, package

可执行的 python 命令可以出现在以下四个地方,第一种是接受键盘输入的程序,后三种都是文件:

  1. interactive: python 交互式界面,也叫做 calculator mode,也就是在命令行输入 python之后出现的界面。每次输入一句,结果在命令行上显示出来。当 python 退出之后,输入过的命令就消失了。
  2. script: python 脚本文件,也就是在命令行输入 python somefile.py里面的那个somefile.py
    1. 毕竟 python 是一种很轻量化的语言,在一定程度上可以起到 shell 的作用,有些命令我们并不想要用完就扔,而是保存起来以便以后重复执行,另外很多命令的组合组合成函数也可以极大地简化工作。在这种语境之下, interactive 和 script 的关系,就好像 Linux 命令行和 bash script 的关系一样。
    2. 但同时 python 又是一种功能很全面的语言,完全可以胜任复杂的面向对象编程。在这种语境之下,script 也可以用来指代 main module,也就是程序执行的主文件和入口,和下面的一般的 module 相区分。
  3. module: python 模块文件,也就是在命令行输入 python -m another 里面的那个 another(注意这里不写拓展名 .py)。按照官方文档的说法,所有 .py 文件都是 module。但是实际上这句话很有误导性,上一节的 main module 和一般的 module 非常不同,下一节会详细展开讲。一般提到 module,都是在强调这个文件定义的变量和函数可以被其他的 python 文件引用。
  4. package: python ,互相关联的 modules 构成的更高一级的可供引用的结构,简单理解就是含有 __init__.py 的文件夹,但是 python 并不是根据文件夹和文件之间的从属关系来确定 package 和 module 之间的关系的,下一节会详细展开讲。

script vs. module

python 同时兼具脚本语言的灵活性,和各种重型语言的功能全面性。因为前者,所以它并不要求程序作者一定要在一个叫做 main.py 的文件里写一个名叫 Main 的类, 然后在里面实现一个 main() 方法。但是因为后者,没写不代表 python 不需要知道一个复杂程序执行的起点。

这个起点就是不带有 -m 参数的 python 命令后面跟着的 .py 文件,这就使得这个文件变得比其他 .py 文件特殊。底层表现就是 python 会不管这个文件的名字叫什么,都将它的 __name__ 属性赋值为 "__main__"。这样,即便这个文件可能是一个大型库中间的一个模块,运行的时候 python 连它的真名都不知道,就更找不到它同级和上下级的其他模块了。

各种普通模块被 python 用到的方法就是通过在主模块 main module(或者说 script)中 import。经过“python 如何读取代码文件”一节中的搜索过程之后找到了所需模块或包,模块的名字、模块之间的关系、模块里定义了哪些属性和函数,就被 python 了解了,从而当主模块召唤他们的时候就知道去哪里找相应的代码。除了在被 import 的时候,python -m 命令的宾语也可以告诉 python 被运行的模块和包的相对关系:python -m sound.formats.wavwrite ,此时 python 执行了 wavwrite.py 中的所有可执行的命令,同时知道从 sound/wavwrite.py 的各个包之间的关系。

absolute import vs. relative import

开头使用已经安装过的包使用的语法全都是绝对引用 (absolute import),表现就是 import 语句里面没有以 . 作为开头的。

另外一种 import 方法叫相对引用 (relative import),. 表示模块所在的文件夹,.. 表示模块的上一级文件夹。主要用在各种明确知道自己是工具代码,而且是一个更高层次结构的组成部分,几乎永远不需要被作为主模块运行的代码。

回到开头例子里的文件结构,假如 sound/effects/surround.py 中想要使用 sound/formats/wavwrite.py 和 sound/effects/echo.py 中的函数,可以写成:


# in sound/effects/surround.py
from ..formats import wavwrite
from . import echo

如何组织代码,以便自己重用

研究终于推进到了准备写论文的阶段了(学渣本质暴露了),写草稿之余,之前几年时间里做过的处理和分析,接下来的一两个月里需要把工作流程规范化之后迅速重做一遍确认。

随手写散落各处的分析代码需要整理到一起,之前试图统一到一个项目之下,结果总是在某个模块引用其他模块的时候遇到报错。于是才有了这篇文章。

以下是 这篇文章 给出的一个推荐的项目文件结构:


|- notebooks/
   |- 01-first-logical-notebook.ipynb
   |- 02-second-logical-notebook.ipynb
   |- prototype-notebook.ipynb
   |- archive/
	  |- no-longer-useful.ipynb
|- projectname/
   |- projectname/
	  |- __init__.py
	  |- config.py
	  |- data.py
	  |- utils.py
   |- setup.py
|- README.md
|- data/
   |- raw/
   |- processed/
   |- cleaned/
|- scripts/
   |- script1.py
   |- script2.py
   |- archive/
      |- no-longer-useful.py
|- environment.yml

学过这篇笔记包含的内容,我才理解作者这样的安排。既然主文件 很难 没办法通过相对引用来找到工具代码,索性就把工具代码写成一个完整可安装的库,然后就像 numpy, pandas 一样在独立的 notebook 和 scripts 中引用。

但是要让一个包可安装,需要创建并编辑 setup.py 这个文件。这篇文章已经够长了,所以这个话题还是下次再说吧。





About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK