Python攻克之路-module
一、模块
例子:调用configpaser模块时,首先在当前的Python编译器去查找,找到后,里面没有ConfigParser()这个方法,直接报错,configparser应该让它去系统查找
import configparser config = configparser.ConfigParser()
在计算机程序的开发过程中,随着程序代码越写越多,在一个文件里代码就会越来越长,越来越不容易维护。
为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里,这样,每个文件包含的代码就相对较少,很多编程语言都采用这种组织代码的方式。在Python中,一个.py文件就称之为一个模块(Module)。
使用模块有什么好处?
最大的好处是大大提高了代码的可维护性。其次,编写代码不必从零开始。当一个模块编写完毕,就可以被其他地方引用。我们在编写程序的时候,也经常引用其他模块,包括Python内置的模块和来自第三方的模块。
使用模块还可以避免函数名和变量名冲突。相同名字的函数和变量完全可以分别存在不同的模块中,因此,我们自己在编写模块时,不必考虑名字会与其他模块冲突。但是也要注意,尽量不要与内置函数名字冲突。点这里查看Python的所有内置函数。
你也许还想到,如果不同的人编写的模块名相同怎么办?为了避免模块名冲突,Python又引入了按目录来组织模块的方法,称为包(Package)。
俗解:代码越写越多,可以使用分组来管理,如把时间相关的放入一个组,以后要调用时间就通过time模块都取,随机数相关的去random取,一层层的取,把功能分开
1、模块分为三种
- python标准库
- 第三方模块(非官方)
- 应用程序自定义模块(自定义py文件)
2、Python模块的调用
简单例子: [root@python3 module]# cat calculate.py #!/usr/local/python3/bin/python3 def add(x,y): return x+y def sub(x,y): return x-y [root@python3 module]# cat bin.py #!/usr/local/python3/bin/python3 import calculate ##调用 print(calculate.add(1,2)) [root@python3 module]# python3 bin.py 3
3、模块的搜索路径
[root@python3 module]# cat bin.py #!/usr/local/python3/bin/python3 import sys ##调用sys模块查看 import calculate print(calculate.add(1,2)) print(sys.path) ##sys.path方法 [root@python3 module]# [root@python3 module]# python3 bin.py #第一个路径就是当前的目录下module 3 ['/root/py/fullstack_s2/week5/module', '/usr/local/python3/lib/python36.zip', '/usr/local/python3/lib/python3.6', '/usr/local/python3/lib/python3.6/lib-dynload', '/usr/local/python3/lib/python3.6/site-packages']
二、分析调用过程和方法
1.实际是Python解释器通过搜索路径把到calculate后,python解释器calculate所有代码都解释一次,无论是什么内容都可以通calculate去引用,调用函数中的add或sub或变量时,是通过calculate这个名字去调用方法
[root@python3 module]# cat calculate.py #!/usr/local/python3/bin/python3 print('cal is ok') ##打印 def add(x,y): return x+y def sub(x,y): return x-y [root@python3 module]# cat bin.py #!/usr/local/python3/bin/python3 import sys import calculate ##只是调用 [root@python3 module]# python3 bin.py cal is ok
变量的调用也是要通过calculate调用
[root@python3 module]# cat calculate.py #!/usr/local/python3/bin/python3 print('cal is ok') x = 4 ## def add(x,y): return x+y def sub(x,y): return x-y [root@python3 module]# cat bin.py #!/usr/local/python3/bin/python3 import sys import calculate print(x) ###直接调用x [root@python3 module]# python3 bin.py cal is ok Traceback (most recent call last): File "bin.py", line 4, in <module> print(x) NameError: name 'x' is not defined ##未定义 [root@python3 module]# cat bin.py #!/usr/local/python3/bin/python3 import sys import calculate print(calculate.x) ##通过calculate的方法调用正常 [root@python3 module]# python3 bin.py cal is ok 4
import多个模块
import time,sys
2.从模块中调用某个方法(from module import func)
场景:模块文件里有很多函数,通过import相当于把所有的都加载一次,可以只调用某个函数来提高使用效率
[root@python3 module]# cat calculate.py #!/usr/local/python3/bin/python3 print('cal is ok') x = 4 def add(x,y): return x+y def sub(x,y): return x-y [root@python3 module]# cat bin.py #!/usr/local/python3/bin/python3 from calculate import add #从calculate中调用add print(add(2,3)) #直接使用函数 [root@python3 module]# python3 bin.py cal is ok 5 [root@python3 module]# cat bin.py #!/usr/local/python3/bin/python3 from calculate import add,sub ##同时调用多个 print(add(2,3)) print(sub(9,3)) [root@python3 module]# python3 bin.py cal is ok 5 6 [root@python3 module]# cat bin.py #!/usr/local/python3/bin/python3 from calculate import add,sub #通过from指定调用时,没有调用x,会报错,表示python并没加载x print(add(2,3)) print(sub(9,3)) print(x) [root@python3 module]# python3 bin.py cal is ok 5 6 Traceback (most recent call last): File "bin.py", line 5, in <module> print(x) NameError: name 'x' is not defined ### [root@python3 module]# cat bin.py #!/usr/local/python3/bin/python3 from calculate import add,sub print(add(2,3)) print(sub(9,3)) print(calculate.x) ##通过calculate去调用,但是还是报错,calculate没被定义,证明只是加载了add,sub [root@python3 module]# python3 bin.py cal is ok 5 6 Traceback (most recent call last): File "bin.py", line 5, in <module> print(calculate.x) NameError: name 'calculate' is not defined ###
3.调用所有的方法,但是不使用模块名称去调用方法(from import *建议少用)
[root@python3 module]# cat bin.py #!/usr/local/python3/bin/python3 from calculate import * #导入所有 print(add(4,9)) print(x) print(sub(3,9)) [root@python3 module]# python3 bin.py cal is ok 13 4 -6
问题分析:from module import *存在的的问题,可能会有以下场景,calculate中有add函数,而调用的Py中也有同样的add函数,实际python解释器是从上到下执行,从from开始,导入模块就加载了def add,而本文件中也有一个def add,所以类似于在同一个Py上给一个变量进行先后赋值,后赋值的生效,*号是加载所有的函数,可能会造成调用的函数与同py中所写的函数的产生一个冲突
[root@python3 module]# cat calculate.py #!/usr/local/python3/bin/python3 print('cal is ok') x = 4 def add(x,y): return x+y def sub(x,y): return x-y [root@python3 module]# cat bin.py #!/usr/local/python3/bin/python3 from calculate import * def add(x,y): return x+y+9 print(add(4,5)) [root@python3 module]# python3 bin.py cal is ok 18
类似同一个py加载一个函数
In [3]: def f(): ...: print('f1') ...: In [4]: def f(): ...: print('f2') ...: In [5]: f() f2
from module import *表示解释器把要加载都执行一次,即使调用多次
[root@python3 module]# cat calculate.py #!/usr/local/python3/bin/python3 print('cal is ok') ## print('cal2 is ok') ## x = 4 def add(x,y): return x+y def sub(x,y): return x-y [root@python3 module]# cat bin.py #!/usr/local/python3/bin/python3 from calculate import * #调用两次 from calculate import * # [root@python3 module]# python3 bin.py cal is ok #只执行一次 cal2 is ok
4.别名的使用as
[root@python3 module]# cat bin.py #!/usr/local/python3/bin/python3 from calculate import add as plus print(plus(4,8)) [root@python3 module]# python3 bin.py 12
三、包package
描述:模块为了组织函数,模块多了要组织模块,为了避免模块名的冲突,python引入了按目录来组织模块的方法
[root@python3 module]# tree web/ web/ #存放模块 ├── __init__.py #区分是文件玩夹还是包 ├── logger.py └── main.py [root@python3 week5]# tree module/ module/ ├── bin.py #bin与web在同一层函数 ├── calculate.py └── web ├── __init__.py ├── logger.py └── main.py
通过bin去调用web中的模块
[root@python3 week5]# cat module/web/logger.py #!/usr/local/python3/bin/python3 def logger(): print('logging in logger') [root@python3 week5]# cat module/bin.py #!/usr/local/python3/bin/python3 from web import logger ##from package import module logger.logger() [root@python3 week5]# python3 module/bin.py logging in logger
两层目录的情况
[root@python3 week5]# mkdir module/web/web2 [root@python3 week5]# touch module/web/web2/__init__.py [root@python3 week5]# mv module/web/logger.py module/web/web2/ [root@python3 week5]# tree module/ module/ ├── bin.py ├── calculate.py └── web ├── __init__.py ├── main.py ├── __pycache__ │ ├── __init__.cpython-36.pyc │ └── logger.cpython-36.pyc └── web2 ├── __init__.py └── logger.py [root@python3 week5]# cat module/bin.py #!/usr/local/python3/bin/python3 from web.web2 import logger ##使用点 logger.logger() [root@python3 week5]# python3 module/bin.py logging in logger
两层目录,再调用模块中的一个方法
[root@python3 week5]# cat module/bin.py #!/usr/local/python3/bin/python3 from web.web2.logger import logger #### logger() [root@python3 week5]# python3 module/bin.py logging in logger
包调用的过程
[root@python3 week5]# cat module/web/__init__.py print('web init') [root@python3 week5]# cat module/bin.py #!/usr/local/python3/bin/python3 import web #调用用包,实际是执行__init__.py的代码 [root@python3 week5]# python3 module/bin.py web init
import模块与包的一个区别
模块调用:
[root@python3 week5]# cat module/calculate.py #!/usr/local/python3/bin/python3 x=9 ##### def add(x,y): return x+y def sub(x,y): return x-y [root@python3 week5]# cat module/bin.py #!/usr/local/python3/bin/python3 import calculate print(calculate.x) ##### [root@python3 week5]# python3 module/bin.py 9
包调用:
[root@python3 week5]# cat module/web/main.py #!/usr/local/python3/bin/python3 x=4 ### [root@python3 week5]# cat module/bin.py #!/usr/local/python3/bin/python3 import web #import一个包,只是执行__init__与其他的模块没关系 print(web.main.x) [root@python3 week5]# python3 module/bin.py web init Traceback (most recent call last): File "module/bin.py", line 3, in <module> print(web.main.x) #####不能以这种方式调用包中的模块中的一个变量 AttributeError: module 'web' has no attribute 'main' [root@python3 week5]# cat module/bin.py #!/usr/local/python3/bin/python3 from web import main ##正常的方式 print(main.x) [root@python3 week5]# python3 module/bin.py web init 4
四、BASEDIR的问题
1、目录结构
[root@python3 week5]# tree atm/ atm/ ├── bin #程序入口,程序有一个执行文件 │ ├── bin.py │ └── __init__.py ├── conf #配置相关 │ ├── __init__.py │ └── setting.py └── module #与逻辑相关 ├── __init__.py ├── logger.py └── main.py 模块之间调用: [root@python3 week5]# cat atm/module/logger.py #!/usr/local/python3/bin/python3 def logging(): print('module logging') [root@python3 week5]# cat atm/module/main.py #!/usr/local/python3/bin/python3 import logger #### def main(): logger.logging() #### main() [root@python3 week5]# python3 atm/module/main.py module logging
2、调用分析
a.logger模块是打印日志,被main模块后调用,可以调用成功,但是使用bin来调用时,可以找到main,但是由于bin和logger不在同一目录下,所以bin调用时打不到logger
[root@python3 atm]# cat module/logger.py #!/usr/local/python3/bin/python3 def logging(): print('module logging') [root@python3 atm]# cat module/main.py #!/usr/local/python3/bin/python3 import logger def main(): logger.logging() [root@python3 atm]# cat bin/bin.py #!/usr/local/python3/bin/python3 from module import main main.main() [root@python3 bin]# python3 bin.py Traceback (most recent call last): File "bin.py", line 2, in <module> from module import main ModuleNotFoundError: No module named 'module' 找不到,但是在pycharm时,它会自动找追加路径到sys.path下,以避免了错误
b.要找到module就要实现自动追加脚本所有的目录路径
__file__:内部变量,取脚本名称
__file__:内部变量,取脚本名称 [root@python3 bin]# cat bin.py #!/usr/local/python3/bin/python3 print(__file__) [root@python3 bin]# python3 bin.py bin.py
path.abspath取绝对路径
[root@python3 bin]# cat bin.py #!/usr/local/python3/bin/python3 import os print(__file__) print(os.path.abspath(__file__)) [root@python3 bin]# python3 bin.py bin.py /root/py/fullstack_s2/week5/atm/bin/bin.py
使用os.path.dirname取得atm的路径
[root@python3 bin]# cat bin.py #!/usr/local/python3/bin/python3 import os print(__file__) print(os.path.abspath(__file__)) print(os.path.dirname(os.path.abspath(__file__))) print(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) [root@python3 bin]# python3 bin.py bin.py /root/py/fullstack_s2/week5/atm/bin/bin.py /root/py/fullstack_s2/week5/atm/bin /root/py/fullstack_s2/week5/atm
使用sys.path.append追加到模块查找到目录
[root@python3 bin]# cat bin.py #!/usr/local/python3/bin/python3 import os,sys BASIC_DIR=os.path.dirname(os.path.dirname(os.path.abspath(__file__))) ### sys.path.append(BASIC_DIR) ### from module import main main.main() [root@python3 bin]# python3 bin.py ##再执行成功 module logging
c.整个程序结构中所需要目录
假设项目为foo,建议有以下的目录结构 [root@python3 day2]# tree foo/ foo/ ├── bin │ └── foo ├── docs │ ├── abc.rst │ └── conf.py ├── foo │ ├── __init__.py │ ├── main.py │ └── test │ ├── __init__.py │ └── test__main.py ├── README ├── requirements.txt └── setup.py
注释:
bin目录:存放项目的一些可执行文件,可以自定义为script/之类
foo目录:存放项目的所有源代码
- 源代码的所有模块、包都应该放在此目录,不要置于顶层目录
- 其子目录test/存放单元测试代码
- 程序的入口最好命名为main.py
docs目录:存放一些文档
setup.py: 安装、部署、打包的脚本(写了一个软件,这个软件要有这的一个执行环境,可以通过这个命令安装好)
requirements.txt:存放软件依赖的外部Python包列表
README: 项目说明文件
README:简要描述该项目信息,让读者快速了解这个项目
说明事项:
a.软件定位,软件的基本功能(开发语言的版本,如有些就使用Python2.7上运行)
b.运行代码的方法,安装环境、启动命令等
c.简要使用说明(如ftp有8个功能,那些已经实现,那些没实现要明说)
d.代码目录结构说明,量多详细点可以说明软件的基本信息
e.常见问题说明
requirements.txt和setup.py
setup.py
描述:使用setup.py来管理代码的打包、安装、部署问题.业界标准写法是用Python流行的打包工具setuptools来管理,
这种方式普遍用于开源项目中,不这这里的核心思想不是用标准化的工具来解决这些问题,而是说,一个项目一定
要有一个安装部署工具,能快速便捷的在一台新机器上将环境安装好、代码都部署好和将程序运行起来
注意事项:
- 安装环境时经常忘记最近又添加一个新的python包,结果一到线运行,程序就会出错
- Python包的版本依赖问题,有时程序中使用是一个版本的Python包,但是官方的已经是最新的包了,通过手动安装可能出错
- 如果依赖包很多,一个个安装这些依赖很费时
- 新手写项目时,将程序运行起来非常麻烦,因为可能经常忘记要怎么安装各种依赖
所以setup.py可以将这些事情自动化起来,提高效率、减少出错的概率
requirements.txt
这个文件存在的目的是:
- 方便开发者维护软件的包依赖,将开发过程中新增的包添加进这个列表中,避免在setup.py安装依赖时漏掉软件包
- 方便读者明确项使用了哪些Python包
这个文件的格式是每一行包含一个包依赖的说明,通常flask>=0.10这种格式,要求是这个格式能被pip识别,这样就可以
简单的通过pip install -r requirements.txt来把所有Python包依赖都装好了https://pip.pypa.io/en/stable/user_guide/
配置文件的使用方法
注意:在上面的目录结构中,没有将conf.py放在源代码目录下,而是放在docs/的目录下
很多项目对配置文件的使用是
- 配置文件写在一个或多个python文件,如这里的conf.py
- 项目中那个模块用到这个配置文件就直接通过import conf这种形式来做代码中使用配置
产生的问题
- 让单元测试变得困难(因为模块内部依赖了外部配置)
- 另一方面配置文件作为用户控制程序的接口,应当可以由用户自由指定该文件的路径
- 程序组组件可复用性太差,因为这种惯穿所有模块的代码编码方式,使得大部分模块依赖conf.py这个文件
更好的配置方式
- 模块的配置都是可以灵活配置的,不受外部配置文件的影响
- 程序配置也是可以灵活控制的(如nginx,mysql的配置文件)
所以,不应当在代码中直接import conf来使用配置文件,上面目录结构的conf.py,是给出一个配置样例,不是在写死的程序中直接引用的配置文件,可以通过给main启动配置路径的方式来让程序读取配置内容,当然,这里的conf.py可以换个类似的名字,比如setting.py.或者可以使用其他格式的内容来编写配置文件,如setting.yaml之类的.
五、__name__变量
分析:if __name__=='__main__': 把程序写好,如何知道那些功能模块,配置等,一般模块之间是调用,本身是不希望调用的,当别人在调用时,会把测试代码也会被一起调用
[root@python3 day2]# cat name/foo.py #!/usr/local/python3/bin/python3 def hi(): print('hi') hi() #一般功能模块只用来调用,不需要执行,但是有时写完会做测试 [root@python3 day2]# cat name/bin.py #!/usr/local/python3/bin/python3 import foo foo.hi() [root@python3 day2]# python3 name/bin.py hi #import foo时会加载一次所有代码 hi
解决方法:使用if __name__=='__main__'
[root@python3 day2]# cat name/foo.py #!/usr/local/python3/bin/python3 def hi(): print('hi') if __name__=='__main__': hi() #可以作测试,而且在其他人使用时,测试的不会显示 [root@python3 day2]# python3 name/foo.py hi [root@python3 day2]# cat name/bin.py #!/usr/local/python3/bin/python3 import foo #foo.hi() [root@python3 day2]# python3 name/bin.py
__name__的原理
[root@python3 day2]# cat name/foo.py #!/usr/local/python3/bin/python3 def hi(): print('hi') print(__name__) [root@python3 day2]# python3 name/foo.py __main__ #本身使用时是__main__ [root@python3 day2]# cat name/bin.py #!/usr/local/python3/bin/python3 import foo [root@python3 day2]# python3 name/bin.py foo
六、模拟实现一个ATM + 购物商场程序
- 额度15000或自定义
- 实现购物商场,买东西加入购物车,调用信用卡接口结账(要输入卡号和密码之类的)
- 可以提现,手续费5%
- 每月22号出账单,每月10号为还款日,过期未还,按欠款总额万分之5每日计息
- 支持多账户登录(多个用户都可以登录购物)
- 支持账户间转账(一个用户转到另一个用户,一个用户减,另一个用户就加上相应的款)
- 记录每月日常消费流水(在购物时加上)
- 提供还款接口
- atm记录操作日志(转账、还款之类的,是银行操作的)
- 提供管理接口,包括添加账户、用户额度、冻结账户等
- 用户认证用装饰器