我们现在已经总结了Python的基本招式和套路,现在可以写一些不那么简单的系统性工程或代码量较大的应用程序。这时候,一个简单的.py文件就会显得过于臃肿,无法承担一个重量级软件开发的重任。这就需要这一章的内容——化繁为简,将功能模块化、文件化,从而可以像搭积木一样,将不同的功能,组建在大型工程中搭建起来。

简单模块化

  最简单的模块化方式,就是把函数、类、常量拆分到不同的文件,把他们放在同一个文件夹,然后使用下面的语句导入

from filename import function_name
from filename import class_name

举个例子吧

#utils.py

def get_sum(a,b):
return a+b
#class_utils.py

class Encoder(object):
def encode(self,s):
return s[::-1] class Decoder(object):
def decode(self,s):
return ''.join(reversed(list(s)))
#main.py

from utils import get_sum
from class_utils import *
print(get_sum(2, 3)) encoder = Encoder()
decoder = Decoder() print(encoder.encode('abc'))
print(decoder.decode('dcba'))

我们把函数get_sum()放在一个文件里(utils.py),而类放在另外一个文件中(class_utils.py),在main函数里就直接调用,from...import...就可以把所需要的类和函数都导入进来。

可是慢慢的,我们发现把所有的文件都放在一个文件夹里也是不好管理的,需要建立多级的目录,就像这样

 .
├── utils
│ ├── utils.py
│ └── class_utils.py
├── src
│ └── sub_main.py
└── main.py

main.py调用子目录的模块时,就需要改变一下代码了

#sub_main.py

import sys
sys.path.append('..') from utils.class_utils import *
from utils import utils as fun print(fun.get_sum(2, 3)) encoder = Encoder()
decoder = Decoder() print(encoder.encode('abc'))
print(decoder.decode('dcba'))

sub_main函数调用的是上层目录下的子目录,就要把这个目录('..')加载到环境变量里。导入的方法也有所不同,导入的方法和模块使用方法可以看看前面写的文章:python之模块的导入

要注意的一点,import同一个模块指挥被执行一次,这样就可以防止重复导入模块出现问题。在很多公司的编程规范中,除了一些极其特殊的情况,import必须位于程序的最前端

还有一点,在许多教程中,我们都要在模块所在的文件夹里建一个__init__.py的文件,内容也可以是空的,主要用来描述包(package)对外暴露的模块接口。不过这是Python2的规范。在Python3以后的版本中,__init__.py并不是必须的。

项目模块化

  很多大的项目,一个项目组的workspace可能有上千个文件,几十万到几百万行代码,上面所说的调用方式已经完全不够用了,下面我们就了解一下模块化的科学组织方式

  首先要回顾一下绝对路径和相对路径的概念。我们在下面的路径下有两个文件

a1/b1/c1/d1/e1/example.txt
a1/b1/c1/d2/e2/example2.txt

如果我们要从e1跳转到e2文件夹,有两种方法

#方法1
cd a1/b1/c1/d2/e2
#方法2
../../d2/e2

方法1用的就是绝对路径,而方法2就是相对路径。其中,用点点(../)代表上一级目录。

  通常一个Python文件在运行的时候,都会有一个运行时的位置,最开始即为这个文件所在的文件夹。而通过下面的语句就可以改变当前Python解释器的位置。而这种方法是不被推荐的。因为一个确定的路径对大型工程来说是非常必要的

import sys
sys.path.append('..')

  首先,因为代码可能会迁移,相对为hi会使得重构既不雅观,也容易出错。因此,在大型的工程中尽可能使用绝对位置是第一要义。对于一个独立的项目,所有的模块的追寻方式,最好从项目的根目录开始追溯,这叫做相对的绝对路径。下面我们创建一个新的项目,项目的结构如下:

 .
├── proto
│ ├── mat.py
├── utils
│ └── mat_mul.py
└── src
└── main.py
#proto/mat.py

class Matrix(object):
def __init__(self,data):
self.data = data
self.n = len(data)
self.m = len(data[0])
#utils/mat_mul.py

from proto.mat import Matrix

def mat_mul(matrix_1:Matrix,matrix_2:Matrix):
assert matrix_1.m == matrix_2.n
n,m,s = matrix_1.n,matrix_1.m,matrix_2.m
result = [[0 for _ in range(n)] for _ in range(s)]
for i in range(n):
for j in range(s):
for k in range(m):
result[i][k] += matrix_1.data[i][j] * matrix_2.data[j][k] return Matrix(result)
#src/main.py

from proto.mat import Matrix
from utils.mat_mul import mat_mul a = Matrix([[1,2],[3,4]])
b = Matrix([[5,6],[7,8]]) print(mat_mul(a,b).data) ##########输出##########
[[19, 22], [43, 50]]

这个案例和前面的例子很像,但是导入的方式是直接用proto.mat导入的因为使用pycharm构建的项目,把不同文件放在不同的子文件夹里,跨模块调用是直接从顶层搜索的。所以必须是新建的项目,不能在已建的项目里新建个文件夹。

但是当我们用命令行调用src文件夹下,执行

python main.py

##########输出##########
Traceback (most recent call last):
File "main.py", line 2, in <module>
from proto.mat import Matrix
ModuleNotFoundError: No module named 'proto'

错了吧!因为Python解释器在遇到import的时候,他会在一个特定的列表里寻找模块,这个列表我们可以看一下

import sys

print(sys.path)

Pycharm在细腻教案项目是就是把根目录设置为列表的里的一项(列表太长了我就不展示了)。这样我们在运行main.py时,import都会从根目录里找相应的包。

那普通的Python运行环境怎么办呢?

第一种:大力出奇迹——强行修改sys.path列表。把根目录直接加进去

import sys
sys.path.append(r'根目录路径')

这样我们的import就畅通无阻了。但是把绝对路径写在代码里是一个非常不推荐的方式(写在配置文件中也会因为找配置文件也需要个路径,于是就进入了个死循环)

第二种方法:修改PYTHONHOME。这里可以提一下Python的Virtual Environment,Python可以通过Virtualenv工具非常方便的常见一个全新的Python运行环境。事实上,对于每一个Python项目来说,最好要有一个独立的运行环境来保持包和模块的纯净性。

在一个虚拟环境里,能找到一个文件叫active,在这个文件的末尾,可以加上下面的内容

export PYTHONPATH='项目路径‘

这样每次通过active激活这个运行环境的时候就会自动将项目的根目录添加到搜索路径中去。

神奇的if __name__ == '__main__'

最后我们来看一下一个非常常见的写法:

if __name__ == '__main__':

这个语法有什么用呢?Python是脚本预压,和C++、Ja最大的不同就是不需要显性的提供main()函数入口。

但是我们在导入文件的过程中,会把所有暴露在外面的代码统统执行一遍。但是大多数时候我们是不想让他们执行的,这时候如果我们想要把一个东西封装成模块,又想让他在需要的时候才执行的话,就把必须要执行的代码放在if __name__ == '__main__'里。因为在__name__是一个内置参数,在我们使用import导入的时候,__name__就会被赋值为该模块的名字,当然就不等于'__main__'了!

思考题

我们在导入的时候有这两种方式

#方式A
from module_name import *
#方式B
import module_name

正常使用的时候哪种比较适合呢?

第一种会把路径下所有的模块导入程序,这就存在一个问题:如果有其他的函数名或类名和导入的模块相同,很容易造成冲突。

而第二种在使用的时候需要用下面的方式调用。相当于加了一层layer,能有效的避免因为名字相同导致的冲突。

model_name.fun()

Python核心技术与实战——十一|程序的模块化的更多相关文章

  1. Python核心技术与实战——十九|一起看看Python全局解释器锁GIL

    我们在前面的几节课里讲了Python的并发编程的特性,也了解了多线程编程.事实上,Python的多线程有一个非常重要的话题——GIL(Global Interpreter Lock).我们今天就来讲一 ...

  2. Python核心技术与实战——六|异常处理

    和其他语言一样,Python中的异常处理是很重要的机制和代码规范. 一.错误与异常 通常来说程序中的错误分为两种,一种是语法错误,另一种是异常.首先要了解错误和异常的区别和联系. 语法错误比较容易理解 ...

  3. Python核心技术与实战——十二|Python的比较与拷贝

    我们在前面已经接触到了很多Python对象比较的例子,例如这样的 a = b = a == b 或者是将一个对象进行拷贝 l1 = [,,,,] l2 = l1 l3 = list(l1) 那么现在试 ...

  4. Python核心技术与实战 笔记

    基础篇 Jupyter Notebook 优点 整合所有的资源 交互性编程体验 零成本重现结果 实践站点 Jupyter 官方 Google Research 提供的 Colab 环境 安装 运行 列 ...

  5. Python核心技术与实战——十六|Python协程

    我们在上一章将生成器的时候最后写了,在Python2中生成器还扮演了一个重要的角色——实现Python的协程.那什么是协程呢? 协程 协程是实现并发编程的一种方式.提到并发,肯很多人都会想到多线程/多 ...

  6. Python核心技术与实战——七|自定义函数

    我们前面用的代码都是比较简单的脚本,而实际工作中是没有人把整个一个功能从头写到尾按顺序堆到一块的.一个规范的值得借鉴的Python程序,除非代码量很少(10行20行左右)应该由多个函数组成,这样的代码 ...

  7. Python核心技术与实战——二一|巧用上下文管理器和with语句精简代码

    我们在Python中对于with的语句应该是不陌生的,特别是在文件的输入输出操作中,那在具体的使用过程中,是有什么引伸的含义呢?与之密切相关的上下文管理器(context manager)又是什么呢? ...

  8. Python核心技术与实战——二十|assert的合理利用

    我们平时在看代码的时候,或多或少会看到过assert的存在,并且在有些code review也可以通过增加assert来使代码更加健壮.但是即便如此,assert还是很容易被人忽略,可是这个很不起眼的 ...

  9. Python核心技术与实战——二十|Python的垃圾回收机制

    今天要讲的是Python的垃圾回收机制 众所周知,我们现在的计算机都是图灵架构.图灵架构的本质,就是一条无限长的纸带,对应着我们的存储器.随着寄存器.异失性存储器(内存)和永久性存储器(硬盘)的出现, ...

随机推荐

  1. @清晰掉 spi协议及工作原理分析

    说明.文章摘自:SPI协议及其工作原理浅析 http://bbs.chinaunix.net/thread-1916003-1-1.html 一.概述. SPI, Serial Perripheral ...

  2. pdf.js浏览中文pdf乱码的问题解决

    由于项目中需要支持移动设备在线浏览pdf,苹果还好,天生支持,但是安卓中就不行了,需要第三方组件的支持. 这里就找到了pdf.js,由于pdf数据太多,开始的时候没法一一测试,所以随便测试打开了几篇没 ...

  3. C#调用windows API实现 smallpdf客户端程序进行批量压缩

    一.背景 Smallpdf 网站针对PDF文件提供了非常齐全的功能:PDF 与 Word.PPT.Excel.JPG 的相互转化.PDF 的压缩.编辑.合并.分割.解密.加密等功能,用户无需注册即可免 ...

  4. CentOS7--删除virbr0

    https://blog.csdn.net/aienjoy/article/details/78994128

  5. JS - 语音

    语音识别 浏览器支持的还不是很好 语音播放 speechSynthesis.speak(new SpeechSynthesisUtterance('只要是活着的东西,神也杀给你看')); 参见 Web ...

  6. 【Hibernate】---【注解】多对多

    一.核心配置文件 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-con ...

  7. Intellij IDEA 常见问题

    右击项目时,没有 Java Class,只能创建其他文件 IDEA 还没有将这个项目识别为 Maven 项目时,会出现这种情况.此时右键无法创建类. 解决办法: 手动为 IDEA 指定项目类型:如果编 ...

  8. RabbitMQ使用(上)

    1. 说明 在企业应用系统领域,会面对不同系统之间的通信.集成与整合,尤其当面临异构系统时,这种分布式的调用与通信变得越发重要.其次,系统中一般会有很多对实时性要求不高的但是执行起来比较较耗时的地方, ...

  9. Pyinstaller-封装python

    1. 当程序中没有调用matplotlib模块 ① pip intall pyinstaller ② 在cmd环境下,pyinstaller -F  xxx.py 2.当程序中调用matplotlib ...

  10. 【C语言--数据结构】线性顺序表

    线性表的本质: 1.线性表(List)是零个或者多个数据元素的集合: 2.线性表中的数据元素之间是有顺序的: 3.线性表中的数据元素个数是有限的: 4.线性表中的数据元素的类型必须相同: 定义: 线性 ...