什么是模块
模块(module) 是一个包含 Python 代码的文件,通常以 .py
结尾。模块可以包含变量、函数、类,甚至其他模块。通过模块,我们可以将代码组织成不同的文件,更好地管理和复用代码。
模块可以分为以下几类:
- 标准库模块:Python 自带的模块,如
math
、os
、sys
等。 - 第三方模块:由其他开发者编写的模块,通常可以通过
pip
安装。 - 自定义模块:用户自己编写的模块。
自定义模块
自定义模块就是一个包含 Python 代码的 .py
文件。你可以将函数、类、变量写入该文件中,然后在其他 Python 文件或脚本中通过 import
语句导入并使用它们。
创建一个 Python 文件:把你想要封装的代码写入一个
.py
文件中。导入自定义模块:在另一个 Python 文件中,使用
import
语句导入这个模块。
创建和使用自定义模块
先创建一个py文件,名为my_module.py
# my_module.py
print("This is my moudle....")
def greet(name):
return f"Hello, {name}!"
def add(a, b):
return a + b
PI = 3.14159
块不仅仅是一个包含函数和类的文件,它还可以包含可执行的语句。这些可执行的语句通常用于模块的初始化,并且只会在模块首次导入时执行。为防止模块被重复导入,Python 采取了一些优化手段。
例如,当我们import一个模块的时候,python回会执行依次该模块中的所有顶层语句(不包括看书内部或者类内部定义的代码),这些语句通常用于初始化模块
拿上述模块举例,如果我们第一次import这个语句的时候,其中的print语句,定义greet和add两个函数的语句,以及定义PI常量的语句将会执行。
我们在同目录下面新建一个demo.py文件,来测试
import my_module
# Output:
This is my moudle....
导入模块时,Python 会为该模块创建一个独立的命名空间,模块内的所有变量、函数和类都属于该命名空间,从而避免与当前代码中的名称发生冲突。
调用模块中的常量/变量
import my_module
import my_module
import my_module
print(my_module.PI)
# Output:
This is my moudle....
3.14159
多次执行import语句,只会初始化一次
调用模块中定义的函数
我们可以直接调用模块中的函数来使用该函数提供的功能
import my_module
# 使用 my_module 中的函数和变量
print(my_module.greet("Alice"))
print(my_module.add(5, 3))
print(my_module.PI)
# Output:
This is my moudle....
Hello, Alice!
8
3.14159
总结:首次导入模块my_module时会做三件事
为源文件(my_module模块)创建新的名称空间
在新创建的命名空间中执行模块中包含的代码
创建名字my_module来引用该命名空间
模块别名
在import导入模块的时候,如果某个模块名字较长,我们可以给该模块给一个别名
# demo.py
import my_module as mm
print(mm.greet("牛老师yyds"))
print(mm.add(5, 3))
print(mm.PI)
# Output:
This is my moudle....
Hello, 牛老师yyds!
8
3.14159
当然对于别名的话,也有其他的用法,我们可以看如下案例:
有两中sql模块mysql和oracle,根据用户的输入,选择不同的sql功能
# mysql.py
def sqlparse():
print('from mysql sqlparse')
# oracle.py
def sqlparse():
print('from oracle sqlparse')
# test.py
db_type=input('>>: ')
if db_type == 'mysql':
import mysql as db
elif db_type == 'oracle':
import oracle as db
db.sqlparse()
同时导入多个模块
import sys, os, re
# 或者
import sys
import os
import re
通过from导入模块
对比import my_module,会将源文件的名称空间'my_module'带到当前名称空间中,使用时必须是my_module.名字的方式
而from 语句相当于import,也会创建新的名称空间,但是将my_module中的名字直接导入到当前的名称空间中,在当前名称空间中,直接使用名字就可以了
# demo.py
from my_module import greet,add
print(greet('牛老师'))
print(add(1,2))
# Output:
This is my moudle....
Hello, 牛老师!
3
如果代码中存在与模块中同名的函数会怎么样呢?
# demo.py
from my_module import greet,add
def add(x,y):
return x*y
print(greet('牛老师'))
print(add(3,2))
# Output:
This is my moudle....
Hello, 牛老师!
6
根据上述案例表明,如果重新定义同名函数,那么模块里面定义的函数会被覆盖
如果想要一次性导入某个模块里面所有的方法,我们可以通过from my_module import *
这样的方式
from mymodule import * 把my_module中所有的不是以下划线()开头的名字都导入到当前位置 大部分情况下我们的python程序不应该使用这种导入方式,因为*你不知道你导入什么名字,很有可能会覆盖掉你之前已经定义的名字。而且可读性极其的差。
那么,如果想要设定from my_module import *
导入哪些模块,我们可以在模块中加上如下代码:
.....
__all__ = ['greet','add']
# 这样在另外一个文件中用from my_module import *就这能导入列表中规定的两个名字
注意:如果my_module.py中的名字前加`_`,即`_`money,则from my_module import *,则`_`money不能被导入
编写好的一个python文件可以有两种用途:
脚本,一个文件就是整个程序,用来被执行
- 模块,文件中存放着一堆功能,用来被导入使用
python为我们内置了全局变量
__name__
,当文件被当做脚本执行时:
__name__ 等于'__main__'
当文件被当做模块导入时:
__name__等于模块名
作用:用来控制.py文件在不同的应用场景下执行不同的逻辑(或者是在模块文件中测试代码)
if __name__ == '__main__':
def fib(n):
a, b = 0, 1
while b < n:
print(b, end=',')
a, b = b, a+b
print()
if __name__ == "__main__":
print(__name__)
num = input('num :')
fib(int(num))
print(globals())
模块的搜索路径
模块的查找顺序是:内存中已经加载的模块->自建模块->sys.path路径中包含的模块
- 在第一次导入某个模块时(比如my_module),会先检查该模块是否已经被加载到内存中(当前执行文件的名称空间对应的内存),如果有则直接引用 ps:python解释器在启动时会自动加载一些模块到内存中,可以使用sys.modules查看
- 如果没有,解释器则会查找同名的内建模块
- 如果还没有找到就从sys.path给出的目录列表中依次寻找my_module.py文件。
编译python文件
为了提高加载模块的速度,python解释器会在__pycache__
目录中下缓存每个模块编译后的版本,格式为:module.version.pyc。通常会包含python的版本号。例如,在CPython3.3版本下,mymodule.py模块会被缓存成`_pycache/my_module.cpython-33.pyc`。这种命名规范保证了编译后的结果多版本共存。
包
包就是一个包含有__init__.py
文件的文件夹,所以其实我们创建包的目的就是为了用文件夹将文件/模块组织起来
需要强调的是:
在python3中,即使包下没有
__init__.py
文件,import 包仍然不会报错,而在python2中,包下一定要有该文件,否则import 包报错创建包的目的不是为了运行,而是被导入使用,记住,包只是模块的一种形式而已,包的本质就是一种模块
为何要使用包
包的本质就是一个文件夹,那么文件夹唯一的功能就是将文件组织起来 随着功能越写越多,我们无法将所以功能都放到一个文件中,于是我们使用模块去组织功能,而随着模块越来越多,我们就需要用文件夹将模块文件组织起来,以此来提高程序的结构性和可维护性
注意事项
关于包相关的导入语句也分为
import
和from ... import ...
两种,但是无论哪种,无论在什么位置,在导入时都必须遵循一个原则:凡是在导入时带点的,点的左边都必须是一个包,否则非法。可以带有一连串的点,如item.subitem.subsubitem
,但都必须遵循这个原则。但对于导入后,在使用时就没有这种限制了,点的左边可以是包,模块,函数,类(它们都可以用点的方式调用自己的属性)。import导入文件时,产生名称空间中的名字来源于文件,import 包,产生的名称空间的名字同样来源于文件,即包下的
__init__.py
,导入包本质就是在导入该文件包A和包B下有同名模块也不会冲突,如A.a与B.a来自俩个命名空间
包的使用
示例文件
glance/ #Top-level package
├── __init__.py #Initialize the glance package
├── api #Subpackage for api
│ ├── __init__.py
│ ├── policy.py
│ └── versions.py
├── cmd #Subpackage for cmd
│ ├── __init__.py
│ └── manage.py
└── db #Subpackage for db
├── __init__.py
└── models.py
文件内容
#文件内容
#policy.py
def get():
print('from policy.py')
#versions.py
def create_resource(conf):
print('from version.py: ',conf)
#manage.py
def main():
print('from manage.py')
#models.py
def register_models(engine):
print('from models.py: ',engine)
使用import导入包
import glance.db.models
glance.db.models.register_models('mysql')
单独导入包名称时不会导入包中所有包含的所有子模块
import glance
# 在导入glance的时候会执行glance下的__init__.py中的代码
glance.cmd.manage.main()
解决方法
# glance/__init__.py
from . import cmd
# glance/cmd/__init__.py
from . import manage
使用from (具体的路径) import (具体的模块)
需要注意的是from后import导入的模块,必须是明确的一个不能带点,否则会有语法错误,如:from a import b.c
是错误语法
from glance.db import models
from glance.db.models import register_models
models.register_models('mysql')
register_models('mysql')
from glance.api import *
想从包api中导入所有,实际上该语句只会导入包api下__init__.py
文件中定义的名字,我们可以在这个文件中定义__all__
x = 10
def func():
print('from api.__init.py')
__all__=['x','func','policy']
from glance.api import *
func()
print(x)
policy.get()
绝对导入和相对导入
- 绝对导入:以glance作为起始
- 相对导入:用.或者..的方式最为起始(只能在一个包中使用,不能用于不同目录内)
绝对导入: 以执行文件的sys.path为起始点开始导入,称之为绝对导入
- 优点: 执行文件与被导入的模块中都可以使用
- 缺点: 所有导入都是以sys.path为起始点,导入麻烦
相对导入: 参照当前所在文件的文件夹为起始开始查找,称之为相对导入
- 符号: .代表当前所在文件的文件加,..代表上一级文件夹,...代表上一级的上一级文件夹
- 优点: 导入更加简单
- 缺点: 只能在导入包中的模块时才能使用