当前位置:   article > 正文

python 全栈开发,Day29(昨日作业讲解,模块搜索路径,编译python文件,包以及包的import和from,软件开发规范)...

假如有两个模块a,b。我可不可以在a模块中import b ,再在b模块中import a?

python 全栈开发,Day29(昨日作业讲解,模块搜索路径,编译python文件,包以及包的import和from,软件开发规范)

 一、昨日作业讲解

先来回顾一下昨日的内容

  1. 1.os模块
  2. 和操作系统交互
  3. 工作目录 文件夹 文件 操作系统命令 路径相关的
  4. 2.模块
  5. 最本质的区别 import会创建一个专属于模块的名字,
  6. 所有导入模块中的都会在这个空间中
  7. import
  8. from import
  9. as 起别名
  10. * 和 __all__

 

作业讲解:
os.listdir() 返回一个列表,里面的每一个元素都是相对路径
值就是文件,或者文件夹

 

使用递归的方式实现

  1. import os
  2. def get_size(dir):
  3. sum_size = 0 # 初始大小
  4. for item in os.listdir(dir): # 返回一个列表,里面的每一个元素都是相对路径
  5. path = os.path.join(dir,item) # 由于item是相对路径的文件或者文件夹,需要join拼出绝对路径
  6. if os.path.isfile(path): # 判断绝对路径是否为文件
  7. sum_size += os.path.getsize(path) # 计算文件大小
  8. else:
  9. sum_size += get_size(path) # 调用自身函数。加上文件夹的大小,固定为4096,空的也是那么大。
  10. return sum_size
  11. ret = get_size('E:\python_script\day26') # 传入一个目录
  12. print(ret)

执行输出:

4950326

 

栈(先进先出)
使用栈的思想完成上面的代码:

  1. import os
  2. def get_size(path):
  3. l = [path] # 文件夹列表
  4. sum_size = 0 # 初始文件大小
  5. while l: # 判断列表是否为空
  6. path = l.pop() # 删除列表最后一个元素,并返回给path l = ['E:\python_script\day26']
  7. for item in os.listdir(path): #遍历列表,path = 'E:\python_script\day26'
  8. path2 = os.path.join(path, item) # 组合绝对路径 path2 = 'E:\python_script\day26\test'
  9. if os.path.isfile(path2): # 判断绝对路径是否为文件
  10. sum_size += os.path.getsize(path2) # 计算文件大小。sum = 文件的大小 + 0
  11. else:
  12. l.append(path2) # 为文件夹时,添加到列表中。再次循环。l = ['E:\python_script\day26\test']
  13. return sum_size
  14. print(get_size('E:\python_script\day26'))

执行输出:

4951192

 

和上面的结果有微小的差异,是因为,当前py文件,增加了几行代码。

查看文件夹属性

大小是一致的,有些windows系统,可能有微小的差异。

 

栈也可以解决深度问题,比如文件夹里面的所有文件统计

l就是一个栈

栈也可以完成递归的功能。
递归比较占用空间,每调用一次,产生一个新的空间
但是栈不会每次产生新的空间,非常节省空间
栈可以解决不确定深度问题
栈的精髓就是先进后出

 

第二题

  1. 思考:假如有两个模块a,b。
  2. 我可不可以在a模块中import b ,再在b模块中import a?

新建文件demo1.py,内容如下:

  1. import my_module
  2. print('demo1')

新建文件my_module.py,内容如下:

  1. import demo1
  2. print('my_module')

执行demo1.py,结果如下:

demo1
my_module
demo1

 

分析:

第一步执行demo1.py中的import my_module

第二步执行my_module.py中的import demo1

第三步执行demo1.py中的import my_module,发现mysql_modulel已经被导入了,那么不会再次导入!

第四步执行demo1.py中的print('demo1'),为什么呢?因为从上至下代码执行原则

第五步执行my_module.py中的print('my_module'),为什么呢?因为从上至下原则

 

引用有一个模块,是为了引用方法

修改demo1.py,增加一个函数

  1. import my_module
  2. def func():
  3. print('ret')
  4. my_module.func2() # 执行函数

修改my_module.py,增加一个函数

  1. import demo1
  2. def func2():
  3. print('in func2')
  4. demo1.func1()

执行demo1.py,输出报错:

AttributeError: module 'my_module' has no attribute 'func2'

为什么呢?

看我上面写的步骤分析,执行第4步时,由于my_module.py中的func2还没有加载到内容

执行第5步时,调用my_module中的func2函数,就会直接报错,提示找不到函数


结论:
代码不会发现循环引用问题
模块中的引用不能成环

看下面一张图

b引用a,c引用b。但是a不能引用c,否则就是一个环。反反复复,无法终止。

2个模块之间引用,那么就产生高层模块和底层模块
根据依赖倒置原则,高层模块不应该依赖底层模块
否则就违反了开发原则。

 

二、模块搜索路径

导入一个模块,就会从sys.path里面的路径中寻找
程序运行时,会将当前路径加入到搜索路径中

目前是demo1.py和my_module.py在同一个文件夹中

将my_module.py文件移动到上一层目录中

  1. import sys
  2. print(sys.path)import my_module # 执行报错

执行就会报错,因为sys.path提供的搜索路径中,找不到my_module.py文件

 如果手动添加一个呢?

  1. import sys
  2. print(sys.path)
  3. sys.path.append('E:\python_script\day26') # 添加搜索路径
  4. import my_module

再次执行,就不会报错了。

能不能导入一个模块,不是靠Pycharm画红线决定的

而是sys.path决定的。

总结:

引入的模块必须满足两个条件
  1.模块名必须满足变量名的规范
  2.被导入的模块所在的位置必须在sys.path所在的搜索路径中

 

模块
  py文件是一个模块
脚本
  py文件也是一个模块

如果一个py文件被导入了 他就是一个模块
如果这个py文件被直接执行 这个被直接执行的文件就是一个脚本

模块
  没有具体的调用过程
  但是能对外提供功能

 

 比如三次登录程序

 创建文件login.py,内容如下:

  1. def log_in():
  2. user = input('>>>')
  3. pwd = input('>>>')
  4. print('三次登录')

 创建文件test.py,内容如下:

  1. import login
  2. login.log_in() # 执行登录

执行文件test.py,效果如下:

>>>111
>>>222
三次登录

 

现在有一个需求:

当login模块被当做脚本执行的时候,能够独立完成登陆功能
当login模块被当做模块导入的时候,需要等待调用才能完成功能

修改login.py

  1. def log_in():
  2. # user = input()
  3. # pwd = input()
  4. print('三次登陆')
  5. print(__name__) 

修改test.py

  1. import login
  2. login.log_in()
  3. print(__name__)

执行test.py,输出:

login
三次登陆
__main__

说明由test.py调用login.py时,输出login,也就是模块名

 

如果login.py想直接执行log_in方法,但是不想被导入时,自动执行。可以做一个if判断

  1. def log_in():
  2. # user = input()
  3. # pwd = input()
  4. print('三次登陆')
  5. if __name__ == '__main__':
  6. log_in()

执行输出:三次登陆

修改test.py,只包含一行

import login

执行test.py,输出为空

 

if __name__ == '__main__': 这句话,永远不会变,它是为了区分脚本和模块

所有的print不应该出现在模块里面,而是出现在if下面的代码中

 

结论:

当一个模块被当做脚本执行的时候,__name__是一个字符串数据类型的'__main__'
当一个模块被当做模块导入的时候,__name__是一个字符串数据类型模块名

 

三、编译python文件

执行导入操作时,会自动创建目录__pycache__

查看里面的文件

那么这些文件是谁创建的呢?

python解释器创建的
当一个文件被当做模块导入的时候,
如果pyc文件不存,python解释器就会创建,存在不会再次被创建

pyc文件 编译文件

python --> 字节码 -->机器码

编译过程
从上到下 编译 成字节码 pyc
从上倒下 解释 执行代码

为什么要编译
  1.一个文件如果作为模块 一定会经常被导入
  2.每次被导入都要经历一个被编译的过程
  3.包.编译耗费时间
  4.所以模块在被第一次导入的时候被编译存在pyc文件里
  5.之后的导入可以直接呐pyc文件中的字节码,就可以直接执行了

 

主要功能:

编译文件 在模块导入的一瞬间 能够提高代码的执行速度
不能提高程序在具体执行的时候的效率

 程序第一次执行,可能会慢一点,因为有一个编译过程

 

补充:dir()函数


内建函数dir是用来查找模块中定义的名字,返回一个有序字符串列表

  1. import my_module
  2. dir(my_module)

如果没有参数,dir()列举出当前定义的名字

dir()不会列举出内建函数或者变量的名字,它们都被定义到了标准模块builtin中,可以列举出它们,

  1. import builtins
  2. dir(builtins)

  

四、包以及包的import和from

包是一种通过使用'.模块名' 来组织python模块名称空间的方式。

1. 无论是import形式还是from...import形式,凡是在导入语句中(而不是在使用时)遇到带点的,都要第一时间提高警觉:这是关于包才有的导入语法
2. 包是目录级的(文件夹级),文件夹是py文件来组成(包的本质就是一个包含__init__.py文件的目录)
3. import导入文件时,产生名称空间中的名字来源于文件,import 包,产生的名称空间的名字同样来源于文件,即包下的__init__.py,导入包本质就是在导入该文件
强调:
  1. 在python3中,即使包下没有__init__.py文件,import 包仍然不会报错,而在python2中,包下一定要有该文件,否则import 包报错
  2. 创建包的目的不是为了运行,而是被导入使用,记住,包只是模块的一种形式而已,包即模块

 

使用Pycharm创建一个包,会自动创建__init__.py文件

所谓的包,就是一个包含__init__.py文件的文件夹

 包A和包B下有同名模块也不会冲突,如A.a与B.a来自俩个命名空间

 

模块是对外提供功能的
如果我写的模块足够强大,能提供的功能足够多
多到一个文件写不
把对外提供的功能,根据提供的内容不同,分成几个文件
把这些文件放在一个文件夹下,就形成了包
比如django框架
是所有框架中,功能最全的
功能:
1.操作数据库的模块
2.和web页面交互的模块
3.登录认证的模块
4.安全的中间件 模块,比如web攻击

django将所有的模块集合成一个包

 

 自动创建目录结构

  1. import os
  2. os.makedirs('glance/api')
  3. os.makedirs('glance/cmd')
  4. os.makedirs('glance/db')
  5. l = []
  6. l.append(open('glance/__init__.py','w'))
  7. l.append(open('glance/api/__init__.py','w'))
  8. l.append(open('glance/api/policy.py','w'))
  9. l.append(open('glance/api/versions.py','w'))
  10. l.append(open('glance/cmd/__init__.py','w'))
  11. l.append(open('glance/cmd/manage.py','w'))
  12. l.append(open('glance/db/models.py','w'))
  13. map(lambda f:f.close() ,l)

执行代码,查看目录结构

文件内容如下:

  1. #policy.py
  2. def get():
  3. print('from policy.py')
  4. #versions.py
  5. def create_resource(conf):
  6. print('from version.py: ',conf)
  7. #manage.py
  8. def main():
  9. print('from manage.py')
  10. #models.py
  11. def register_models(engine):
  12. print('from models.py: ',engine)

手动将内容复制到对应的py文件中去

这里面的每一个文件夹,都是包
包里面的每一个py文件,都是模块

 

新建一个文件new.py

目录结构如下:

  1. ./
  2. ├── glance
  3. │   ├── api
  4. │   │   ├── __init__.py
  5. │   │   ├── policy.py
  6. │   │   └── versions.py
  7. │   ├── cmd
  8. │   │   ├── __init__.py
  9. │   │   └── manage.py
  10. │   ├── db
  11. │   │   └── models.py
  12. │   └── __init__.py
  13. └── new.py 

new.py想用glance目录下的api文件夹下的policy模块

修改new.py

  1. from glance.api import policy
  2. policy.get()

执行输出:

from policy.py

注意事项

1.关于包相关的导入语句也分为import和from ... import ...两种,但是无论哪种,无论在什么位置,在导入时都必须遵循一个原则:凡是在导入时带点的,点的左边都必须是一个包,否则非法。可以带有一连串的点,如item.subitem.subsubitem,但都必须遵循这个原则。

2.对于导入后,在使用时就没有这种限制了,点的左边可以是包,模块,函数,类(它们都可以用点的方式调用自己的属性)。

3.对比import item 和from item import name的应用场景:
如果我们想直接使用name那必须使用后者。

 

也可以用这种写法:

  1. import glance.api.policy
  2. glance.api.policy.get()

执行输出:

from policy.py

对于点没有约束,对比2种方式,使用from方式,就比较简单了。

 

from ... import ...
需要注意的是from后import导入的模块,必须是明确的一个不能带点,否则会有语法错误,如:from a import b.c是错误语法
这样就报错了

  1. from glance import api.policy
  2. policy.get()

下面的方式,也是不对的

  1. import glance
  2. glance.api.policy.get()

症结就是导入一个包,并没有把包里面的所有内容,导入进来

导入一个包,相当于执行了这个包下面的__init__.py文件

修改glance下面的__init__.py文件

import api

手动执行__init__.py文件,没有报错

修改new.py

  1. import glance
  2. glance.api.policy.get()

执行new.py,提示报错

ImportError: No module named 'api'

为什么?
因为sys.path,路径中找不到api。__init__.py 执行时,在它的工作目录中可以找到api
但是new.py所在的工作目录,无法直接找到api目录,所以就报错了

 

绝对导入和相对导入

我们的最顶级包glance是写给别人用的,然后在glance包内部也会有彼此之间互相导入的需求,这时候就有绝对导入和相对导入两种方式:

绝对导入:以glance作为起始

相对导入:用.或者..的方式最为起始(只能在一个包中使用,不能用于不同目录内)

 

修改glance目录下的__init__.py文件

from glance import api

修改api目录下的__init__.py 

from glance.api import policy

再次执行new.py,输出:

from policy.py

  

为什么呢?因为首先执行了glance目录下的__init__.py文件,再执行了api目录下的__init__.py 

所以就能找到policy模块

 

所有导入,都是以glance为基础
这就是绝对路径导入

 

这个时候,新建一个文件new_pac
将glance目录剪切到new_pac

当前目录结构如下:

  1. ./
  2. ├── new_pac
  3. │   └── glance
  4. │   ├── api
  5. │   │   ├── __init__.py
  6. │   │   ├── policy.py
  7. │   │   ├── __pycache__
  8. │   │   │   ├── __init__.cpython-35.pyc
  9. │   │   │   └── policy.cpython-35.pyc
  10. │   │   └── versions.py
  11. │   ├── cmd
  12. │   │   ├── __init__.py
  13. │   │   └── manage.py
  14. │   ├── db
  15. │   │   └── models.py
  16. │   ├── __init__.py
  17. │   └── __pycache__
  18. │   └── __init__.cpython-35.pyc
  19. └── new.py

如果new.py想调用new_pac目录下glance文件夹下面的内容呢?
修改new.py文件

  1. from new_pac import glance
  2. glance.api.policy.get()

执行报错:

ImportError: No module named 'glance'

为什么?因为glance下的所有__init__.py路径不对,都得改
修改glance目录下的__init__.py

from new_pac.glance import api

修改api目录下的__init__.py 

from new_pac.glance.api import policy

再次执行new.py,输出

from policy.py

 

但是这样太麻烦了,有缺点
一旦上级目录发生变化,那么所有的init_py,都得修改

总结:

绝对路径
  被直接执行的文件与包的关系必须是固定的,
    一旦发生改变,包内的所有关系都要重新指定
  跨包引用的问题,无法解决

 

如果policy想要执行manage呢?

修改policy.py,内容如下:

  1. from new_pac.glance.cmd import manage
  2. def get():
  3. print('from policy.py')
  4. manage.main()

执行报错:

ImportError: No module named 'new_pac'

不能单独执行policy.py
它取决于sys.path

 

不改变new.py内容,执行new.py,输出:

from manage.py
from policy.py

结果正常。

特别需要注意的是:可以用import导入内置或者第三方模块(已经在sys.path中),但是要绝对避免使用import来导入自定义包的子模块(没有在sys.path中),应该使用from... import ...的绝对或者相对导入,且包的相对导入只能用from的形式。

 

相对导入

用.或者..的方式最为起始(只能在一个包中使用,不能用于不同目录内)

好处就是,比方是你电脑开发的。换了另外一台电脑,也依然可以正常运行,不需要修改文件路径

再次执行创建文件的脚本

  1. import os
  2. os.makedirs('glance/api')
  3. os.makedirs('glance/cmd')
  4. os.makedirs('glance/db')
  5. l = []
  6. l.append(open('glance/__init__.py','w'))
  7. l.append(open('glance/api/__init__.py','w'))
  8. l.append(open('glance/api/policy.py','w'))
  9. l.append(open('glance/api/versions.py','w'))
  10. l.append(open('glance/cmd/__init__.py','w'))
  11. l.append(open('glance/cmd/manage.py','w'))
  12. l.append(open('glance/db/models.py','w'))
  13. map(lambda f:f.close() ,l)

再次补充文件内容

  1. #policy.py
  2. def get():
  3. print('from policy.py')
  4. #versions.py
  5. def create_resource(conf):
  6. print('from version.py: ',conf)
  7. #manage.py
  8. def main():
  9. print('from manage.py')
  10. #models.py
  11. def register_models(engine):
  12. print('from models.py: ',engine)

新建文件new2.py

目录结构如下:

  1. ./
  2. ├── glance
  3. │ ├── api
  4. │ │ ├── __init__.py
  5. │ │ ├── policy.py
  6. │ │ └── versions.py
  7. │ ├── cmd
  8. │ │ ├── __init__.py
  9. │ │ └── manage.py
  10. │ ├── db
  11. │ │ └── models.py
  12. │ └── __init__.py
  13. └── new2.py 

修改glance下的__init__.py,内容如下:

from . import api

点表示当前路径

api目录的__init__.py,内容如下:

from . import policy

修改new2.py,内容如下:

  1. import glance
  2. glance.api.policy.get()

执行new2.py,输出:

from policy.py

 

新建目录new_pac2,将目录glance剪切到new_pac2下面

目录结构如下:

  1. ./
  2. ├── new2.py
  3. └── new_pac2
  4. └── glance
  5. ├── api
  6. │   ├── __init__.py
  7. │   ├── policy.py
  8. │   └── versions.py
  9. ├── cmd
  10. │   ├── __init__.py
  11. │   └── manage.py
  12. ├── db
  13. │   └── models.py
  14. └── __init__.py

 

修改new2.py,内容如下:

  1. from new_pac2 import glance
  2. glance.api.policy.get()

再次执行new2.py,输出:

from policy.py

居然没有报错,666啊!

 

修改policy.py,导入versions模块

  1. from . import versions
  2. def get():
  3. print('from policy.py')
  4. versions.create_resource('userinfo')

执行new2.py,输出:

from version.py: userinfo
from policy.py

 

手动执行policy.py,输出:

ImportError: attempted relative import with no known parent package

它不能当成脚本执行

 

结论:

在一个py文件中使用了相对路径引入一个模块
那么这个文件就不能被当成脚本运行了

 

修改policy.py,导入manage模块

  1. from . import versions
  2. from .. cmd import manage
  3. def get():
  4. print('from policy.py')
  5. versions.create_resource('userinfo')
  6. manage.main()

点点表示上一级目录

执行new2.py,输出:

  1. from version.py: userinfo
  2. from manage.py
  3. from policy.py

  

new2.py 只能关联到glance包

 

from glance.api import *

在讲模块时,我们已经讨论过了从一个模块内导入所有*,此处我们研究从一个包导入所有*。
此处是想从包api中导入所有,实际上该语句只会导入包api下__init__.py文件中定义的名字,我们可以在这个文件中定义__all___:

 

修改policy.py

  1. from . import versions
  2. from ..cmd import manage
  3. __all__ = ['get']
  4. def get():
  5. print('from policy.py')
  6. versions.create_resource('userinfo')
  7. manage.main()

修改api.py,内容如下:

  1. from . import policy
  2. __all__ = ['policy']

修改new2.py

from new_pac2.glance.api import *

执行输出:

from version.py: userinfo
from manage.py

 

总结:

包就是py模块的集合
自带__init__.py文件
  py2 包中必须有一个__init__.py文件
  py3 不存在也可以
能不能导入一个包:要看sys.path中的路径下有没有这个包
从包中导入模块: 把包与包之间的关系写清楚,精确到模块,就一定能导入
直接导入一个包,并不会导入包下的模块,而是执行这个包下的__init__.py文件
如果对导入还有更高的要求
  可以对包中的__init__.py文件做定义
  绝对路径导入的方式
  相对路径导入的方式 使用相对路径导入的模块不能作为脚本执行

 

sys.path是所有模块的核心
是导入的关键

 

记住下面的:

  1. #导入具体的文件
  2. #1.使用from
  3. from glance.api import policy
  4. policy.get()
  5. #
  6. #1.使用import加别名
  7. import glance.api.policy as policy
  8. policy.get()

保你2年平安...

 

五、软件开发规范

比如校园管理系统,目录结构如下:

开始程序为start.py

 

为什么要设计好目录结构?
"设计项目目录结构",就和"代码编码风格"一样,属于个人风格问题。
设计一个层次清晰的目录结构,就是为了达到以下两点:
  1.可读性高: 不熟悉这个项目的代码的人,一眼就能看懂目录结构,知道程序启动脚本是哪个,测试目录在哪儿,配置文件在哪儿等等。从而非常快速的了解这个项目。

  2.可维护性高: 定义好组织规则后,维护者就能很明确地知道,新增的哪个文件和代码应该放在什么目录之下。这个好处是,随着时间的推移,代码/配置的规模增加,项目结构不会混乱,仍然能够组织良好。

 

所以,我认为,保持一个层次清晰的目录结构是有必要的。更何况组织一个良好的工程目录,其实是一件很简单的事儿。

 

关于README的内容

这个我觉得是每个项目都应该有的一个文件,目的是能简要描述该项目的信息,让读者快速了解这个项目。

它需要说明以下几个事项:

1.软件定位,软件的基本功能。
2.运行代码的方法: 安装环境、启动命令等。
3.简要的使用说明。
4.代码目录结构说明,更详细点可以说明软件的基本原理。
5.常见问题说明。

我觉得有以上几点是比较好的一个README。在软件开发初期,由于开发过程中以上内容可能不明确或者发生变化,并不是一定要在一开始就将所有信息都补全。但是在项目完结的时候,是需要撰写这样的一个文档的。

 

可以参考Redis源码中Readme的写法,这里面简洁但是清晰的描述了Redis功能和源码结构。

https://github.com/antirez/redis#what-is-redis

 

posted @ 2018-04-26 19:54 肖祥 阅读( ...) 评论( ...) 编辑 收藏
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/喵喵爱编程/article/detail/945333
推荐阅读
相关标签
  

闽ICP备14008679号