我们现在已经总结了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. Spring Security 报There is no PasswordEncoder mapped for the id "null"

    查了下发现是spring security 版本在5.0后就要加个PasswordEncoder了 解决办法 在securityConfig类下加入NoOpPasswordEncoder,不过官方已经 ...

  2. 使用collection查询集合属性

    介绍resultMap中使用collection查询集合属性 业务需求,查询部门中的多个人员 public class Department { private Integer id; private ...

  3. 来自数据源的 String 类型的给定值不能转换为指定目标列的类型 nvarchar

    .TrimEnd() 怀疑是否SqlBulkCopy是否存在某种bug,故而在系统中改写代码,用单个sql的插入数据方式,用循环逐条导入.结果是没问题.难道真的是SqlBulkCopy有某种bug?上 ...

  4. struts2默认action设置了却访问不到

    1.错误原因 我的package中共有两个action,第一个是默认action,用于访问的action不存在时候的出错处理,第二个是通配符方式写的action,name采用*_*形式的全通配符.配置 ...

  5. seaborn用heatmap画热度图

    原文链接 https://blog.csdn.net/m0_38103546/article/details/79935671

  6. Window7系统安装和使用MySql

    win7系统MySql安装和使用教程 首先下载mysql安装包 点击下载mysql v5.7.1 解压 下载完毕后解压在D盘 路径为D:\mysql-5.7.13-winx64,然后进入这个目录,新建 ...

  7. RSTP基础配置

    本实验模拟公司网络场景.S3和S4是接入层交换机,负责用户的接入,S1和S2是汇聚层交换机,四台交换机组成一个环形网络.为了防止网络中出现环路,产生网络风暴,所有交换机上都需要运生成树协议.同时为了加 ...

  8. Docker数据持久化及实战(Nginx+Spring Boot项目+MySQL)

    Docker数据持久化: Volume: (1)创建mysql数据库的container docker run -d --name mysql01 -e MYSQL_ROOT_PASSWORD= my ...

  9. SVN的各种符号含义,svn的星号,感叹号,问号等含义

    黄色感叹号(有冲突):--这是有冲突了,冲突就是说你对某个文件进行了修改,别人也对这个文件进行了修改,别人抢在你提交之前先提交了,这时你再提交就会被提示发生冲突,而不允许你提交,防止你的提交覆盖了别人 ...

  10. php读取excel文件并导入数据库(表头任意设定)

    最近收到一个很奇葩的需求,要求上传excel员工工资表,表格表头不固定,导入后字段名为表头的拼音,每月导入一次,当月重复导入则覆盖现有的当月表头,并且可以按照在界面上按照月份筛选显示,我写的代码主要包 ...