主页 > 手机  > 

python中如何组织项目工程文件

python中如何组织项目工程文件
一、项目工程文件目录

一个典型的Python项目工程目录结构可以帮助你更好地组织代码、资源和测试,从而使得项目更加清晰和易于维护。

my_project/ │ ├── my_project/ # 项目的主代码包 │ ├── __init__.py # 包初始化文件 │ ├── module_1.py # 示例模块1 │ └── module_2.py # 示例模块2 │ ├── tests/ # 测试代码目录 │ ├── __init__.py # 测试包初始化文件 │ ├── test_module_1.py # 模块1的单元测试 │ └── test_module_2.py # 模块2的单元测试 │ ├── docs/ # 项目文档 │ └── ... # 文档文件或子目录 │ ├── setup.py # 构建和安装脚本 ├── pyproject.toml # 定义构建系统要求(PEP 518) ├── requirements.txt # 列出项目依赖 ├── README.md # 项目说明文档 ├── LICENSE # 许可证信息 └── .gitignore # Git版本控制忽略规则

目录和文件说明

my_project/:这是你的Python项目的主要源代码所在的地方。每个模块可以是单独的.py文件或者更复杂的子包。tests/:这个目录用于存放所有与项目相关的测试代码。保持测试代码独立于源代码有助于保持代码的整洁性。docs/:这里存放项目的文档资料,可以包括使用指南、API文档等。setup.py 和 pyproject.toml:这两个文件用于定义如何打包你的项目以及它的依赖关系。setup.py是传统的配置文件,而pyproject.toml是根据PEP 518引入的新标准。requirements.txt:列出项目依赖的所有外部库及其版本,便于在其他环境中复现项目的依赖环境。README.md:提供项目的简要介绍、安装步骤、使用方法等信息。LICENSE:包含项目的开源许可协议文本。.gitignore:指定Git不应跟踪的文件模式,通常包括编译生成的文件、日志文件、本地配置等。 二、模块

在初步学习python之前我们基本上是用命令行通过 python 解释器来编程,如果你从 Python 解释器退出再进入,那么你定义的所有的方法和变量就都消失了。

为此 Python 提供了一个办法,把这些定义存放在文件中,为一些脚本或者交互式的解释器实例使用,这个文件被称为模块。一个.py文件就称之为一个模块(Module)。

模块的作用

代码复用:将常用的功能封装到模块中,可以在多个程序中重复使用。命名空间管理:模块可以避免命名冲突,不同模块中的同名函数或变量不会互相干扰。代码组织:将代码按功能划分到不同的模块中,使程序结构更清晰。 1.典型python文件 #!/usr/bin/python3 # 文件名: hello.py # -*- coding: utf-8 -*- ' a test module ' __author__ = 'Michael Liao' import sys print('命令行参数如下:') for i in sys.argv: print(i) print('\n\nPython 路径为:', sys.path, '\n') Shebang (#! /usr/bin/python3):

这一行被称为Shebang或hashbang,它位于文件的绝对开头。它的作用是指定该脚本使用的解释器路径。在这个例子中,#!/usr/bin/python3告诉操作系统这个脚本应该使用Python 3来执行。这对于在Unix、Linux和macOS等系统上直接运行脚本特别有用。

编码声明 (# – coding: utf-8 --):

这行注释指定了源代码文件的字符编码格式为UTF-8。虽然Python 3默认使用UTF-8编码,但明确指定编码可以避免一些潜在的编码问题,尤其是在处理非ASCII字符时。

模块文档字符串 (’ a test module '):

紧接在文件头部之后的是一个字符串字面量,它通常被用作模块级别的文档字符串(docstring)。这是对整个模块的目的和功能的简短描述,有助于其他开发者快速了解该模块的作用。

作者信息 (author = ‘Michael Liao’):

__author__是一个特殊变量,用于标识脚本或模块的作者。这是一种约定俗成的做法,尽管不是必须的,但它有助于识别模块的创作者或维护者。

导入模块 (import sys):

import sys语句用于导入Python标准库中的sys模块。通过导入这个模块,你可以访问与Python解释器及其环境交互的功能,比如命令行参数(sys.argv)和Python搜索路径(sys.path)。

2.引入模块 (1)import 语句

想使用 Python 源文件,只需在另一个源文件里执行 import 语句,语法如下:

import module1[, module2[,... moduleN]

当解释器遇到 import 语句,如果模块在当前的搜索路径就会被导入。

搜索路径时一个解释器会先进行搜索的所有目录的列表。如想要导入模块 support,需要把命令放在脚本的顶端:

#!/usr/bin/python3 # Filename: support.py def print_func( par ): print ("Hello : ", par) return

test.py 引入 support 模块:

#!/usr/bin/python3 # Filename: test.py # 导入模块 import support # 现在可以调用模块里包含的函数了 support.print_func("World") (2)模块的搜索路径

当导入一个模块时,Python 会按照以下顺序查找模块:

当前目录。环境变量 PYTHONPATH 指定的目录。Python 标准库目录。.pth 文件中指定的目录。 >>> ['/root', '/usr/lib/python3.4', '/usr/lib/python3.4/plat-x86_64-linux-gnu', '/usr/lib/python3.4/lib-dynload', '/usr/local/lib/python3.4/dist-packages', '/usr/lib/python3/dist-packages'] (3)from … import 语句

Python 的 from 语句让你从模块中导入一个指定的部分到当前命名空间中,语法如下:

from modname import name1[, name2[, ... nameN]]

例如,要导入模块 fibo 的 fib 函数,使用如下语句:

# Filename: fibo.py # 斐波那契(fibonacci)数列模块 def fib(n): # 定义到 n 的斐波那契数列 a, b = 0, 1 while b < n: print(b, end=' ') a, b = b, a+b print() def fib2(n): # 返回到 n 的斐波那契数列 result = [] a, b = 0, 1 while b < n: result.append(b) a, b = b, a+b return result >>> from fibo import fib, fib2 >>> fib(500) 1 1 2 3 5 8 13 21 34 55 89 144 233 377

这个声明不会把整个fibo模块导入到当前的命名空间中,它只会将fibo里的fib函数引入进来。

(4)给模块起别名

使用 as 关键字为模块或函数起别名:

import numpy as np # 将 numpy 模块别名设置为 np from math import sqrt as square_root # 将 sqrt 函数别名设置为 square_root (5)from … import * 语句

把一个模块的所有内容全都导入到当前的命名空间也是可行的,只需使用如下声明:

from modname import *

这提供了一个简单的方法来导入一个模块中的所有项目。

不推荐,容易引起命名冲突。

(6)__name__ 属性

当我们在命令行运行模块文件时,Python解释器把一个特殊变量__name__置为__main__,而如果在其他地方导入该模块时,if判断将失败,因此,这种if测试可以让一个模块通过命令行运行时执行一些额外的代码,最常见的就是运行测试。

if __name__=='__main__': test()

一个模块被另一个程序第一次引入时,其主程序将运行。

#!/usr/bin/python3 # Filename: using_name.py if __name__ == '__main__': print('程序自身在运行') else: print('我来自另一模块') 运行输出如下:

如果我们想在模块被引入时,模块中的某一程序块不执行,我们可以用 __name__ 属性来使该程序块仅在该模块自身运行时执行。

$ python using_name.py 程序自身在运行 $ python >>> import using_name 我来自另一模块 >>> 3.标准模块

以下是一些 Python3 标准库中的模块:

os 模块:os 模块提供了许多与操作系统交互的函数,例如创建、移动和删除文件和目录,以及访问环境变量等。

sys 模块:sys 模块提供了与 Python 解释器和系统相关的功能,例如解释器的版本和路径,以及与 stdin、stdout 和 stderr 相关的信息。

time 模块:time 模块提供了处理时间的函数,例如获取当前时间、格式化日期和时间、计时等。

datetime 模块:datetime 模块提供了更高级的日期和时间处理函数,例如处理时区、计算时间差、计算日期差等。

random 模块:random 模块提供了生成随机数的函数,例如生成随机整数、浮点数、序列等。

math 模块:math 模块提供了数学函数,例如三角函数、对数函数、指数函数、常数等。

re 模块:re 模块提供了正则表达式处理函数,可以用于文本搜索、替换、分割等。

json 模块:json 模块提供了 JSON 编码和解码函数,可以将 Python 对象转换为 JSON 格式,并从 JSON 格式中解析出 Python 对象。

urllib 模块:urllib 模块提供了访问网页和处理 URL 的功能,包括下载文件、发送 POST 请求、处理 cookies 等。

(1)操作系统接口

os 模块提供了不少与操作系统相关联的函数,例如文件和目录的操作。

import os # 获取当前工作目录 current_dir = os.getcwd() print("当前工作目录:", current_dir) # 列出目录下的文件 files = os.listdir(current_dir) print("目录下的文件:", files) (2)文件通配符

glob 模块提供了一个函数用于从目录通配符搜索中生成文件列表:

>>> import glob >>> glob.glob('*.py') ['primes.py', 'random.py', 'quote.py'] (3)命令行参数

通用工具脚本经常调用命令行参数。这些命令行参数以链表形式存储于 sys 模块的 argv 变量。例如在命令行中执行 “python demo.py one two three” 后可以得到以下输出结果:

>>> import sys >>> print(sys.argv) ['demo.py', 'one', 'two', 'three'] 三、命名空间

命名空间(Namespace)是从名称到对象的映射,大部分的命名空间都是通过 Python 字典来实现的。

命名空间提供了在项目中避免名字冲突的一种方法。各个命名空间是独立的,没有任何关系的,所以一个命名空间中不能有重名,但不同的命名空间是可以重名而没有任何影响。

一般有三种命名空间:

内置名称(built-in names), Python 语言内置的名称,比如函数名 abs、char 和异常名称 BaseException、Exception 等等。全局名称(global names),模块中定义的名称,记录了模块的变量,包括函数、类、其它导入的模块、模块级的变量和常量。局部名称(local names),函数中定义的名称,记录了函数的变量,包括函数的参数和局部定义的变量。(类中定义的也是)

Python 的查找顺序为:局部的命名空间 -> 全局命名空间 -> 内置命名空间。

1.命名空间的生命周期

命名空间的生命周期取决于对象的作用域,如果对象执行完成,则该命名空间的生命周期就结束。

因此,我们无法从外部命名空间访问内部命名空间的对象。

# var1 是全局名称 var1 = 5 def some_func(): # var2 是局部名称 var2 = 6 def some_inner_func(): # var3 是内嵌的局部名称 var3 = 7

如下图所示,相同的对象名称可以存在于多个命名空间中。

四、作用域

作用域就是一个 Python 程序可以直接访问命名空间的正文区域。

在一个 python 程序中,直接访问一个变量,会从内到外依次访问所有的作用域直到找到,否则会报未定义的错误。

Python 中,程序的变量并不是在哪个位置都可以访问的,访问权限决定于这个变量是在哪里赋值的。

变量的作用域决定了在哪一部分程序可以访问哪个特定的变量名称。

1.作用域的分类

Python 的作用域一共有 4 种,分别是:

有四种作用域:

L(Local):最内层,包含局部变量,比如一个函数/方法内部。E(Enclosing):包含了非局部(non-local)也非全局(non-global)的变量。比如两个嵌套函数,一个函数(或类) A 里面又包含了一个函数 B ,那么对于 B 中的名称来说 A 中的作用域就为 nonlocal。G(Global):当前脚本的最外层,比如当前模块的全局变量。B(Built-in): 包含了内建的变量/关键字等,最后被搜索。

LEGB 规则(Local, Enclosing, Global, Built-in):Python 查找变量时的顺序是: L –> E –> G –> B。

Local:当前函数的局部作用域。Enclosing:包含当前函数的外部函数的作用域(如果有嵌套函数)。Global:当前模块的全局作用域。Built-in:Python 内置的作用域。 在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,再者去内置中找。

g_count = 0 # 全局作用域 def outer(): o_count = 1 # 闭包函数外的函数中 def inner(): i_count = 2 # 局部作用域 2.全局变量和局部变量

定义在函数内部的变量拥有一个局部作用域,定义在函数外的拥有全局作用域。

局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问。

在函数内部声明的变量只在函数内部的作用域中有效,调用函数时,这些内部变量会被加入到函数内部的作用域中,并且不会影响到函数外部的同名变量,如下实例:

#!/usr/bin/python3 total = 0 # 这是一个全局变量 # 可写函数说明 def sum( arg1, arg2 ): #返回2个参数的和." total = arg1 + arg2 # total在这里是局部变量. print ("函数内是局部变量 : ", total) return total #调用sum函数 sum( 10, 20 ) print ("函数外是全局变量 : ", total) 3.global 和 nonlocal关键字

当内部作用域想修改外部作用域的变量时,就要用到 global 和 nonlocal 关键字了。

以下实例修改全局变量 num:

#!/usr/bin/python3 num = 1 def fun1(): global num # 需要使用 global 关键字声明 print(num) num = 123 print(num) fun1() print(num) # 以上实例输出结果: 1 123 123

如果要修改嵌套作用域(enclosing 作用域,外层非全局作用域)中的变量则需要 nonlocal 关键字了,如下实例:

#!/usr/bin/python3 def outer(): num = 10 def inner(): nonlocal num # nonlocal关键字声明 num = 100 print(num) inner() print(num) outer() # 以上实例输出结果: 100 100 总结 全局变量在函数外部定义,可以在整个文件中访问。局部变量在函数内部定义,只能在函数内访问。使用 global 可以在函数中修改全局变量。使用 nonlocal 可以在嵌套函数中修改外部函数的变量。 4.模块间公开与私有的作用域

在一个模块中,我们可能会定义很多函数和变量,但有的函数和变量我们希望给别人使用,有的函数和变量我们希望仅仅在模块内部使用。

类似java中public和private修饰符,而在Python中没有修饰符关键字,是通过_前缀来实现的。

正常的函数和变量名是公开的(public),可以被直接引用,比如:abc,x123,PI等;

类似__xxx__这样的变量是特殊变量,可以被直接引用,但是有特殊用途,比如上面的__author__,__name__就是特殊变量,hello模块定义的文档注释也可以用特殊变量__doc__访问,我们自己的变量一般不要用这种变量名;

类似_xxx和__xxx这样的函数或变量就是非公开的(private),不应该被直接引用,比如_abc,__abc等;

之所以我们说,private函数和变量“不应该”被直接引用,而不是“不能”被直接引用,是因为Python并没有一种方法可以完全限制访问private函数或变量,但是,从编程习惯上不应该引用private函数或变量。

private函数或变量不应该被别人引用,那它们有什么用呢?请看例子:

def _private_1(name): return 'Hello, %s' % name def _private_2(name): return 'Hi, %s' % name def greeting(name): if len(name) > 3: return _private_1(name) else: return _private_2(name)

我们在模块里公开greeting()函数,而把内部逻辑用private函数隐藏起来了,这样,调用greeting()函数不用关心内部的private函数细节,这也是一种非常有用的代码封装和抽象的方法,即:

外部不需要引用的函数全部定义成private,只有外部需要引用的函数才定义为public。

五、包

如果不同的人编写的模块名相同怎么办?为了避免模块名冲突,Python又引入了按目录来组织模块的方法,称为包(Package)。

包是一种管理 Python 模块命名空间的形式,采用"点模块名称"。

比如一个模块的名称是 A.B, 那么他表示一个包 A中的子模块 B 。

举个例子,一个abc.py的文件就是一个名字叫abc的模块,一个xyz.py的文件就是一个名字叫xyz的模块。

现在,假设我们的abc和xyz这两个模块名字与其他模块冲突了,于是我们可以通过包来组织模块,避免冲突。方法是选择一个顶层包名,比如mycompany,按照如下目录存放:

mycompany ├─ __init__.py ├─ abc.py └─ xyz.py

引入了包以后,只要顶层的包名不与别人冲突,那所有模块都不会与别人冲突。现在,abc.py模块的名字就变成了mycompany.abc,类似的,xyz.py的模块名变成了mycompany.xyz。

就好像使用模块的时候,你不用担心不同模块之间的全局变量相互影响一样,采用点模块名称这种形式也不用担心不同库之间的模块重名的情况。

请注意,每一个包目录下面都会有一个__init__.py的文件,这个文件是必须存在的,否则,Python就把这个目录当成普通目录,而不是一个包。

__init__.py可以是空文件,也可以有Python代码,因为__init__.py本身就是一个模块,而它的模块名就是mycompany。

最简单的情况,放一个空的 :file:__init__.py就可以了。当然这个文件中也可以包含一些初始化代码或者为__all__变量赋值。

1.案例分析

不妨假设你想设计一套统一处理声音文件和数据的模块(或者称之为一个"包")。

现存很多种不同的音频文件格式(基本上都是通过后缀名区分的,例如: .wav,:file:.aiff,:file:.au,),所以你需要有一组不断增加的模块,用来在不同的格式之间转换。

并且针对这些音频数据,还有很多不同的操作(比如混音,添加回声,增加均衡器功能,创建人造立体声效果),所以你还需要一组怎么也写不完的模块来处理这些操作。

这里给出了一种可能的包结构(在分层的文件系统中):

sound/ 顶层包 __init__.py 初始化 sound 包 formats/ 文件格式转换子包 __init__.py wavread.py wavwrite.py aiffread.py aiffwrite.py auread.py auwrite.py ... effects/ 声音效果子包 __init__.py echo.py surround.py reverse.py ... filters/ filters 子包 __init__.py equalizer.py vocoder.py karaoke.py ...

在导入一个包的时候,Python 会根据 sys.path 中的目录来寻找这个包中包含的子目录。

2.如何导入包中的内容? (1)每次导入一个模块

用户可以每次只导入一个包里面的特定模块,比如:

import sound.effects.echo

这将会导入子模块:sound.effects.echo。 他必须使用全名去访问:

sound.effects.echo.echofilter(input, output, delay=0.7, atten=4) (2)导入子模块

还有一种导入子模块的方法是:

from sound.effects import echo

这同样会导入子模块: echo,并且他不需要那些冗长的前缀,所以他可以这样使用:

echo.echofilter(input, output, delay=0.7, atten=4) (3)导入函数变量

还有一种变化就是直接导入一个函数或者变量:

from sound.effects.echo import echofilter

同样的,这种方法会导入子模块: echo,并且可以直接使用他的 echofilter() 函数:

echofilter(input, output, delay=0.7, atten=4)

注意当使用 from package import item 这种形式的时候,对应的 item 既可以是包里面的子模块(子包),或者包里面定义的其他名称,比如函数,类或者变量。

import 语法会首先把 item 当作一个包定义的名称,如果没找到,再试图按照一个模块去导入。如果还没找到,抛出一个 :exc:ImportError 异常。

反之,如果使用形如 import item.subitem.subsubitem 这种导入形式,除了最后一项,都必须是包,而最后一项则可以是模块或者是包,但是不可以是类,函数或者变量的名字。

3.从一个包中导入*

如果我们使用 from sound.effects import * 会发生什么呢?

Python 会进入文件系统,找到这个包里面所有的子模块,然后一个一个的把它们都导入进来。

但这个方法在 Windows 平台上工作的就不是非常好,因为 Windows 是一个不区分大小写的系统。

在 Windows 平台上,我们无法确定一个叫做 ECHO.py 的文件导入为模块是 echo 还是 Echo,或者是 ECHO。

为了解决这个问题,我们只需要提供一个精确包的索引。

(1)关于 __all__ 变量

导入语句遵循如下规则:如果包定义文件 __init__.py 存在一个叫做 __all__ 的列表变量,那么在使用 from package import * 的时候就把这个列表中的所有名字作为包内容导入。

作为包的作者,可别忘了在更新包之后保证 __all__ 也更新了啊。

以下实例在 file:sounds/effects/__init__.py 中包含如下代码:

__all__ = ["echo", "surround", "reverse"]

这表示当你使用from sound.effects import *这种用法时,你只会导入包里面这三个子模块。

如果 __all__ 真的没有定义,那么使用from sound.effects import *这种语法的时候,就不会导入包 sound.effects 里的任何子模块。他只是把包sound.effects和它里面定义的所有内容导入进来(可能运行__init__.py里定义的初始化代码)。

这会把 init.py 里面定义的所有名字导入进来。并且他不会破坏掉我们在这句话之前导入的所有明确指定的模块。看下这部分代码:

import sound.effects.echo import sound.effects.surround from sound.effects import *

这个例子中,在执行 from...import 前,包 sound.effects 中的 echo 和 surround 模块都被导入到当前的命名空间中了。(当然如果定义了 __all__ 就更没问题了)

通常我们并不主张使用 * 这种方法来导入模块,因为这种方法经常会导致代码的可读性降低。不过这样倒的确是可以省去不少敲键的功夫,而且一些模块都设计成了只能通过特定的方法导入。

(2)推荐做法

记住,使用 from Package import specific_submodule 这种方法永远不会有错。事实上,这也是推荐的方法。除非是你要导入的子模块有可能和其他包的子模块重名。

如果在结构中包是一个子包(比如这个例子中对于包sound来说),而你又想导入兄弟包(同级别的包)你就得使用导入绝对的路径来导入。比如,如果模块sound.filters.vocoder 要使用包 sound.effects 中的模块 echo,你就要写成 from sound.effects import echo。

from . import echo from .. import formats from ..filters import equalizer

无论是隐式的还是显式的相对导入都是从当前模块开始的。主模块的名字永远是"__main__",一个Python应用程序的主模块,应当总是使用绝对路径引用。

包还提供一个额外的属性__path__。这是一个目录列表,里面每一个包含的目录都有为这个包服务的__init__.py,你得在其他__init__.py被执行前定义哦。可以修改这个变量,用来影响包含在包里面的模块和子包。

这个功能并不常用,一般用来扩展包里面的模块。

六、打包python程序成exe文件 1.打包单个文件 (1)安装PyInstaller:

你可以通过pip来安装PyInstaller,如果还没有安装的话:

pip install pyinstaller (2)打包你的程序:

使用以下命令来打包你的Python脚本。–onefile选项用于创建单个可执行文件。

pyinstaller --onefile your_script.py

这将在dist目录下生成一个单独的.exe文件,你可以将这个文件分发给用户。

2.和配置文件一起打包

有时候程序文件有配置文件,需要一起打包

[smtp] server = smtp.example port = 587 username = your_email@example password = your_password from_addr = monitor@example to_addrs = admin1@example ;admin2@example (1)安装依赖 pip install pyinstaller configparser (2)创建打包规范文件(monitor.spec) # -*- mode: python -*- block_cipher = None a = Analysis(['monitor_tool.py'], pathex=[], binaries=[], datas=[('config.ini', '.'), ('ips.txt', '.')], hiddenimports=[], hookspath=[], runtime_hooks=[], excludes=[], win_no_prefer_redirects=False, win_private_assemblies=False, cipher=block_cipher, noarchive=False) pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) exe = EXE(pyz, a.scripts, [], exclude_binaries=True, name='NetworkMonitor', debug=False, bootloader_ignore_signals=False, strip=False, upx=True, console=False ) coll = COLLECT(exe, a.binaries, a.zipfiles, a.datas, strip=False, upx=True, name='NetworkMonitor')

注意:生产环境使用时建议将console=False改为True以便查看运行状态

(3)执行打包命令 pyinstaller monitor.spec
标签:

python中如何组织项目工程文件由讯客互联手机栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“python中如何组织项目工程文件