python可重用结构——模块 和 包

一、概述

python可重用模块实际上就是,包含函数或者类的python脚本,对于一个大型的脚本而言,经常需要将其功能细化,将实现不同功能的代码放在不同的脚本中实现,在其他的脚本中以模块的形式,实现细化的功能,以便脚本的维护和重用。

二、python模块的基本用法

模块是包含函数和其他语句的python脚本文件,它以“.py”为后缀名,用作模块的python脚本与其他脚本,并没有什么区别。

2.1 导入模块

在python中可以使用以下两种方法导入模块和模块中的函数

  • import 模块名
  • import 模块名as新名字
  • from 模块名import函数名
           其中,使用import是将整个模块导入,而使用from则是将模块中某一个函数或者名字导入,而不是整个模块。使用import和from导入模块还有一个不同:要想使用import导入模块中的函数,则必须以 " 模块名+ " . " +函数名" 的形式调用函数;而要想使用from导入模块中的某个函数,则可以直接使用函数名调用,不必在前面加上模块名称。

在这里插入图片描述

代码:
import math   # 使用import导入math模块
a = math.sqrt(9)   # 使用模块中的sqrt函数
print(a)
执行结果:
3.0

代码:
sqrt(9)       # 直接使用sqrt函数,调用失败
执行结果:
NameError: name 'sqrt' is not defined

代码:
from math import sqrt  # 使用from导入math模块中的sqrt函数
b = sqrt(9) # 直接使用函数名,调用函数
print(b)
执行结果:
3.0

使用from导入模块中的函数,调用是不再需要函数名,如果想将模块中的所有函数都使用这种方式导入,可以在from中使用“*”通配符,表示将模块中的所有函数导入。

代码:
from math import *
a = sqrt(9)  # 求9的平方根
b = sin(30) # 求x的正弦
print( "%.1f\n %.1f\n" %(a,b))
执行结果:
3.0
-1.0

在python2.x中,还可以通过内置函数reload重新载入模块,reload可以在模块被修改的情况下不必关闭python而重新载入模块,而使用reload重载模块时,该模块必须事先已经被导入。
在python3中,reload函数已经被删除,而重新载入模块,则需要使用imp模块中的reload函数

import os
import imp
imp.reload(os)

2.2 编写一个简单的模块

mymodule.py文件内容:

def show():
    print("this is mymodule")
    
test.py文件内容:

import mymodule
mymodule.show()

执行test.py 结果:
this is mymodule

在模块中除了定义函数外还可以定义变量,变量同样可以在其他脚本中使用。

模块定义脚本中,添加一个变量
def show():
    print("this is mymodule")
name = 'mymodule'
测试脚本:
import mymodule
mymodule.show()
print(mymodule.name) # 打印脚本
mymodule.name = 'change' # 重新赋值
print(mymodule.name)
执行结果:
this is mymodule
mymodule
change

三、在哪里查找模块

      编写好的模块只有被 Python 找到才能被导入。上一节中编写的模块和调用模块的脚本位于同一个目录中,因此不需要进行设置就能被Python 找到并导入。如果在该目录中新建一 个module目录,并且把mymodule,py 转移到mymodule 目录中,
在这里插入图片描述
再次在Windows 的命令窗口中运行usemodule,py,则输出如下:
Traceback (most recent call last): File "E:/python练习/test.py", line 8, in <module> from venv.mymodule import mymodule ModuleNotFoundError: No module named 'venv.mymodule'
      运行脚本出错,Python 解释器没有找到mymodule模块。在导入模块时,Python 解释器首先在当前目录中查找要导入的模块。如果未找到模块,则Python解释器会从sys模块中path变量指定的目录中查找导入模块。如果在以上所有目录中都未找到导入的模块,则会输出出错信息。
以下代码是使用sys path输出Pythion 的模块查找路径:

import sys
print(sys.path)

path路径:
['E:\\python练习', 
'E:\\python练习', 
'C:\\Users\\lenovo\\AppData\\Local\\Programs\\Python\\Python37-32\\python37.zip', 
'C:\\Users\\lenovo\\AppData\\Local\\Programs\\Python\\Python37-32\\DLLs', 
'C:\\Users\\lenovo\\AppData\\Local\\Programs\\Python\\Python37-32\\lib', 
'C:\\Users\\lenovo\\AppData\\Local\\Programs\\Python\\Python37-32', 
'C:\\Users\\lenovo\\AppData\\Local\\Programs\\Python\\Python37-32\\lib\\site-packages', 
'E:\\软件\\pycharm\\PyCharm 2018.3.4\\helpers\\pycharm_matplotlib_backend']

     在脚本中可以向sys.path中添加模块查找路径,以下所示脚本中,将当前目录下的mymodule子目录添加到sys.path中,并从module目录中导入mymodule模块,代码如下:

修改test代码:
import os
import sys
mymodulepath = os.path.abspath(os.path.dirname(os.getcwd()))+'\\mymodule'   # 获取mymodule.py所在目录
print(mymodulepath)
sys.path.append(mymodulepath)   # 添加path路径
print(sys.path  )
import mymodule
mymodule.show()
print(mymodule.name)
mymodule.name = 'change'
print(mymodule.name)
执行结果:
E:\python练习\venv\mymodule   
['E:\\python练习\\venv\\Scripts',
'E:\\软件\\pycharm\\PyCharm 2018.3.4\\helpers\\pydev',
'E:\\python练习',
 'E:\\软件\\pycharm\\PyCharm 2018.3.4\\helpers\\third_party\\thriftpy', '
 E:\\软件\\pycharm\\PyCharm 2018.3.4\\helpers\\pydev', 'C:\\Users\\lenovo\\.PyCharm2018.3\\system\\cythonExtensions', 'E:\\python练习\\venv\\Scripts', 'C:\\Users\\lenovo\\AppData\\Local\\Programs\\Python\\Python37-32\\python37.zip', 'C:\\Users\\lenovo\\AppData\\Local\\Programs\\Python\\Python37-32\\DLLs', 'C:\\Users\\lenovo\\AppData\\Local\\Programs\\Python\\Python37-32\\lib', 'C:\\Users\\lenovo\\AppData\\Local\\Programs\\Python\\Python37-32', 'C:\\Users\\lenovo\\AppData\\Local\\Programs\\Python\\Python37-32\\lib\\site-packages',
'E:\\软件\\pycharm\\PyCharm 2018.3.4\\helpers\\pycharm_matplotlib_backend',
'E:\\python练习\\venv\\mymodule']
E:\python练习\venv\mymodule    # 怎加的path路径
E:\python练习\venv\Scripts
E:\python练习\venv     
this is mymodule
mymodule
change

关于window下获取目录可以参照这篇博客:博客链接

代码:
import os
print(os.path.abspath(__file__))  # 返回当前的绝对路径
print(os.getcwd()) # 返回当前所在目录
print(os.path.abspath(os.path.join(os.getcwd(),"..")))  # 从里往外看-->(返回当前目录,".."上级目录)os.join结合起来,在用os.path.abpath返回绝对路径。S
print(os.path.dirname(os.getcwd())) # os.path.dirname当前路径的目录
print(os.path.abspath(os.path.join(os.getcwd(),"../.."))) # 返回上上级目录
print(os.path.dirname(os.path.dirname(os.getcwd())))  # 两次嵌套
执行结果:
E:\python练习\venv\test.py
E:\python练习\venv
E:\python练习
E:\python练习
E:\python练习
E:\python练习
  • 在上一节的例子中,在python2运行完test.py会发现,moudle目录中除了mymodule,py文件外还多了一个mymodule.pyc文件。其实,mymodule.pyc就是Python将mymodule.py编译成字节码的文件。虽然Python是脚本语言,但Python可以将脚本编译成字节码的形式。对于模块,Python总是在第一次调用后就将其编译成字节码的形式,以提高脚本的启动速度。Ppthon在导入模块时会查找模块的字节码文件,如果存在则将编译后的模块的修改时间同模块的修改时间相比较。如果两者的修改时间不相符,则Pyhton将重新编译模块,以保证两者相符。被编译的脚本也是可以直接运行的。
  • 在Python3中,如果在py_ compile.compile函数中不指定第2个参数,则将在当前目录新建一个 名为“__pycache__'的目录,并在这个目录中生成“被编译模块名.cpython-37.pyc"的pytc字节码文件.
    在这里插入图片描述
  • 当然,没有必要去刻意编译Python脚本。不过,由于Python是脚本,如果不想将泪则可以发布编译后的脚本,这样能起到定的保护源文件的作用。对于非模块的脚本,Python 不会在运行脚本后将其编译成字节码的形式。可以使用compile模块。以下代码可以将其中的mymodule.py文件编译成.pyc文件。
# file compile.py 和mymodule在同一个目录下
import py_compile
py_compile.compile("mymodule.py","mymodule.pyc" )

在这里插入图片描述
另外,还可以通过Python的命令行选项将脚本优化编译。Python编译的优化选项有以下两个。

◆-O 该选项对脚本的优化不多,编译后的脚本以“.pyo"为扩展名。凡是以“.pyo"
为扩展名的Python字节码都是经过优化的。
◆-OO 该选项对脚本优化的程度较大。使用该标志可以使编译的Python脚本更小。使
用该选项可能导致脚本运行错误,因此,应谨慎使用。
可以通过在命令行中输入以下命令将mymodule.py优化编译。

python -O mymodule.py
python -OO mymodule.py

四、模块也可以独立运行

每个Python脚本在运行时都有一个__name__ 属性 ( name前后均是两条下画线)。在脚本中通过对name 属性值的判断,可以让脚本在作为导入模块和独立运行时都可以正确运行。在Python中,如果脚本作为模块被导入,则其__name__ 属性被设置为模块名; 如果脚本独立运行,则其__name__ 属性被设置为 __main__ 。 因此可以通过__name__ 属性 来判断脚本的运行状态。

五、如何查看模块提供的函数名

如果想查看模块提供的函数名,可以通过内置函数dir()来进行操作,以下代码获得sys模块中的函数名:

>>> import sys
>>> dir(sys)
['__breakpointhook__', '__displayhook__', '__doc__', '__excepthook__', '__interactivehook__', '__loader__', 
'__name__', '__package__', '__spec__', '__stderr__', '__stdin__', '__stdout__', '_clear_type_cache', 
'_current_frames', '_debugmallocstats', '_enablelegacywindowsfsencoding', '_framework', '_getframe', '_git', '_home', 
'_xoptions', 'api_version', 'argv', 'base_exec_prefix', 'base_prefix', 'breakpointhook', 'builtin_module_names', 
'byteorder', 'call_tracing', 'callstats', 'copyright', 'displayhook', 'dllhandle', 'dont_write_bytecode', 'exc_info', 
'excepthook', 'exec_prefix', 'executable', 'exit', 'flags', 'float_info', 'float_repr_style', 'get_asyncgen_hooks', 
'get_coroutine_origin_tracking_depth', 'get_coroutine_wrapper', 'getallocatedblocks', 'getcheckinterval', 
'getdefaultencoding', 'getfilesystemencodeerrors', 'getfilesystemencoding', 'getprofile', 'getrecursionlimit', 'getrefcount', 
'getsizeof', 'getswitchinterval', 'gettrace', 'getwindowsversion', 'hash_info', 'hexversion', 'implementation', 'int_info', 
'intern', 'is_finalizing', 'maxsize', 'maxunicode', 'meta_path', 'modules', 'path', 'path_hooks', 'path_importer_cache', 
'platform', 'prefix', 'ps1', 'ps2', 'set_asyncgen_hooks', 'set_coroutine_origin_tracking_depth', 'set_coroutine_wrapper', 
'setcheckinterval', 'setprofile', 'setrecursionlimit', 'setswitchinterval', 'settrace', 'stderr', 'stdin', 'stdout', 'thread_info', 
'version', 'version_info', 'warnoptions', 'winver']
>>>

dir函数原型如下:
dir ( [object] )
其参数含义如下:
object 可选参数,要列举的模块名
如果不向dir()函数传递参数,那么dir()函数将返回当前脚本所在的所有名字列表


>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'sys']
>>> a = [ 1,2,3]
>>> dir()
['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'a', 'sys']

六、用包来管理多个模块

  • 在java中,通过包将比不同的类组织在一起,类似的,在python中也提供了包的功能,可以使用包来管理多个模块。使用包的好处在于可以有效避免名字冲突,便于包的维护管理。Python 中的模块包可以通过路径导入模块。
  • 包的组成:
    包可以看作处于同一目录中的模块。在Python中首先使用目录名,然后再使用模块名导入所需要的模块。在包的每个目录中都必须包含一个名为__init__ py ( init的前后均是两条下画线)的文件。__init__ py 可以是一个空文件,仅用于表示该目录是一个包。__init__ py 的主要用途是设置__all__ 变量以及所包含的包初始化所需的代码。 对于在from中使用 “*”通配符导入包内所有名字时,在__init__ py 中设置__all__ 变量可以保证名字的正确导入。

一个简单的python包的目录组成如图所示:
在这里插入图片描述
如果需要导入B目录中的a.py模块,可以使用以下语句之一:

from A.B import a
import A.B.a
  • 有了包的概念就可以很好地解决模块查找路径的问题。只要将所有的模块放在当前目录中的某一文件夹内, 然后在该文件夹中新建一个空的__init__.py文件,就可以通过目录结构的层次导人所需的模块。而不必像前边的例子那样将子目录的路径添加到sys.path列表中。
    在这里插入图片描述
包的内部引用
  • Python包中的模块也可能需要相互引用。对于上图所示的位于C目录中的b.py,如果要引用同样位于C目录中的a.py,则可以使用以下语句。
    import a
    如果位于C目录中的b.py,想要引用位于B目录中的a.py,则需要使用以下语句。
    from A.B import a

简单示例:
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章