Python基础-12.模块与包

TOC

一、 Python模块

把这些定义存放在文件中,为一些脚本或者交互式的解释器实例使用,这个文件被称为模块。
模块是一个包含所有你定义的函数和变量的文件,其后缀名是.py。模块可以被别的程序引入,以使用该模块中的函数等功能。这也是使用 python 标准库的方法。

模块的作用:

  • 代码复用:将常用的功能封装到模块中,可以在多个程序中重复使用。
  • 命名空间管理:模块可以避免命名冲突,不同模块中的同名函数或变量不会互相干扰。
  • 代码组织:将代码按功能划分到不同的模块中,使程序结构更清晰。

模块导入使用(import)

模块使用示例

我们可以通过import关键字来导入模块。
【示例】导入标准库的sys模块使用

import sys  # 导入sys模块
# 使用sys模块功能
print('当前系统:', sys.platform)
'''运行结果如下
当前系统: darwin
'''

import使用方法

编写一个模块文件formula.py作为示例演示,定义一个类,中有很多方法。
文件内容如下:

class Demo:
    def add(self, a, b):
        print(a + b)

    def reduce(self, a, b):
        print(a - b)

    def ride(self, a, b):
        print(a * b)

    def exc(self, a, b):
        print(a / b)

1.import

导入模块最简单的方法,导入了之后,以模块名为起点调用模块的成员。
【示例】直接导入模块并使用模块里面的成员,这里的成员就是类

import formula  # 导入formula模块
# 创建对象
demo = formula.Demo()
# 调用模块方法
demo.add(3, 4)
demo.reduce(56, 43)

使用import...as能够定义模块的别名,使用别名来调用模块的类和方法。

import formula as f  # 导入formula模块并设置别名为f
# 创建对象
demo = f.Demo()
# 调用模块方法
demo.add(3, 4)
demo.reduce(56, 43)

2.from…import

import的区别就是from让你从模块中导入一个指定的部分到当前命名空间中。
【示例】从模块中导入指定的成员,这里的成员就是类

from formula import Demo  # 从formula模块中导入Demo类
# 创建对象
demo = Demo()
# 调用类里的方法
demo.ride(5, 6)
demo.exc(8, 2)

import导入方法一样,from有也能使用from...import...as这样的语法糖来定义导入类的别名。
【示例】

from formula import Demo as demo  # 从formula模块中导入Demo类并设置别名为demo
# 调用类里的方法
demo().ride(5, 6)
demo().exc(8, 2)

3.导入多模块多成员

当我们需要导入多个模块内容的时候需要导入的时候,我们无须写多个导入语句,我们可以用逗号,来间隔,同行写入多个模块名即可。

import os, sys

当我们需要从某个模块导入全部的内容时我们可以使用from...import *语句来导入某个模块下的全部内容。

from modname import *

自定义模块

根据封装,我们可以自己封装类或者方法编写模块,然后在其他代码文件中导入使用。

自定义模块示例
假设我们编写两个Python文件,我们在hello.py中封装方法,并在test.py中导入使用方法。
文件目录结构如下:

test
├── hello.py
└── test.py

hello.py文件内容如下:

def say(name):
    print('你好!', name)

test.py文件内容如下:

import hello  # 导入hello模块
# 调用hello中的say方法
hello.say('Jack')
'''运行结果如下
你好! Jack
'''

补充说明:除了使用import导入,还可以使用from...import或者from...import...as的方法来导入模块使用,随着导入的方式不同,使用模块的方法写法也会有所变化。

【示例】使用其他方式导入

# from...import导入方式
from hello import say
say('Lucy')
# from...import...as导入方式
import hello as h
h.say('Bob')
'''运行结果如下
你好! Lucy
你好! Bob
'''

模块导入路径

模块搜索路径

当导入一个模块,Python会按照一下顺序查找模块:

  • 1.当前执行脚本所在路径
  • 2.环境变量PYTHONPATH指定的目录
  • 3.Python标准库路径
  • 4.site-packages第三方库路径
  • 5.sys.path列表中的其他路径

我们可以通过以下方法获取这些路径,如下所示:

import os  # 导入一个标准库模块
import sys  # 导入sys模块
import yaml  # 导入一个三方库模块

print('当前目录位置:', os.path.dirname(__file__))
print('环境变量PYTHONPATH值:', os.getenv('PYTHONPATH'))
print('标准库os模块位置:', os.path.dirname(os.__file__))
print('三方库yaml模块位置:', os.path.dirname(yaml.__file__))
print('sys.path列表:', sys.path)
'''运行结果如下
当前目录位置: /Users/devops/Desktop/Walking_Life/devops-tools/test
环境变量PYTHONPATH值: /Users/.../devops-tools
标准库os模块位置: /Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10
三方库yaml模块位置: /Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/yaml
sys.path列表: ['/Users/.../devops-tools/test', '/Users/.../devops-tools', ...]
'''

模块添加方式

1.临时添加模块完整路径

在所需要的代码项目中往sys.path中临时添加模块文件的存储位置,如下所示:

import sys
sys.path.append('/path/to/module_path')

2.保存模块到指定位置

就是将模块放到标准库或者三方库的存放路径下,这些目录都会存放到sys.path中,我们只需要将模块存放到sys.path列表中的任一目录都可以。

cp formula.py /Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/

3.设置PYTHONPATH环境变量

Linux系统

echo 'export PYTHONPATH="/path/to/module_path"' >> /etc/profile
source /etc/profile

MacOS系统

echo 'export PYTHONPATH="/path/to/module_path"' >> .zshrc

Windows电脑
直接在系统属性高级环境变量中设置,如下图所示:
Python.001

二、深入模块

__name__属性

每个模块都有__name__属性,该属性是每个模块自带的内置属性,代表模块的名字。
Python文件的运行方式分两种:

  • 直接当作脚本运行.py
  • .py文件被作为模块导入使用

两种情况下__name__的值是不一样,一个实验来证明。如下所示:
【示例】编写两个.py文件,一个作为模块编写方法输出__name__属性值,在另一个文件中输出当前的__name__属性值,调用模块方法对比__name__值的结果
hello.py文件内容如下:

def show_name():
    print('作为模块运行__name__的值:', __name__)

test.py文件内容如下:

from hello import show_name
print('直接运行__name__的值:', __name__)
show_name()  # 调用模块获取__name__属性值
'''运行结果如下
直接运行__name__的值: __main__
作为模块运行__name__的值: hello
'''

如果我们想在模块被引用时,模块中的某一代码块不运行,当作为主程序才运行的时候,我们可以利用__name__属性通过以下方式来编写。
【示例】当__name__属性值为__main__时运行

if __name__ == '__main__':
    print('作为主程序才被运行...')

补充说明:比如:我们在编写模块的时候我们想在当前文件下进行调试类和方法的时候。

__all__属性

__all__属性是一个模块级别的特殊变量,用来控制from...import *导入全部内容时能够导入哪些名字,类似一个白名单。
在默认情况下,模块中没有定义__all__,那么会导入所有不是以下划线_开头的内容(类、函数、变量等)。
【示例】

# demo.py
a = 1           # from...import *会被导入
_b = 2          # from...import *时不被导入
def _a(): pass  # from...import *时不被导入
def b(): pass   # from...import *会被导入

定义__all__属性

# demo.py
__all__ = ["a", "b"]

x = 1
def a(): pass
def b(): pass

在上面的定义中就只会导入a()b(),x不会被导入。

dir()函数

dir()函数是一个内置的函数,该函数可以找到模块中定义的所有名称。
【示例】根据上面__all__属性写的demo示例查看demo模块中所有内容

import demo  
print('demo模块中被定义的内容:', dir(demo))
'''运行结果如下
demo模块中被定义的内容: ['__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'a', 'b', 'x']
'''

温馨提示:即使x变量未被__all__定义也会被dir()函数显示出来。

其他常用属性

1.__doc__属性

获取模块的文档字符串(docstring),如果模块开头写了字符串,就会被存到这里;没有写则为None
【示例】

"""这是一个测试其他模块的测试文件"""

import formula
print('formula模块的文档内容:', formula.__doc__)
print('当前文件的文档内容:', __doc__)
'''运行结果如下
formula模块的文档内容: 这是一个数学计算加、减、乘、除模块
当前文件的文档内容: 这是一个测试其他模块的测试文件
'''

2.__file__属性

模块文件的路径(字符串),用于定位当前模块的实际位置。
【示例】

import os
print(os.__file__)  # 查看导入模块的文件路径
print(__file__)     # 查看当前文件的文件路径

3.__dict__属性

模块的命名空间,用一个字典保存模块里的所有内容(变量、函数、类等)。等价于globals()
【示例】

import demo
print(demo.__dict__.keys())  # demo模块里的所有成员

4.__package__属性

表示模块所属的包名,如果模块是顶层模块,__package__ == ""(空字符串),如果模块是包里的子模块,值就是包的名字。

三、Python包

包的概念

当我们的模块有很多很多成千上万的时候我们都将模块.py文件放在当前目录下看起来就会非常的不方便管理分辨哪些模块中有哪些功能。而且,使用模块可以有效避免变量名或函数名重名引发的冲突,但是如果模块名重复怎么办呢?
我们也可以在每个模块下创建很多类来进一步划分每个模块的功能全面性,但是我们在进行维护的过程中类和代码过多了之后,我们要找到对应的功能代码要翻很长很长的行数。
于是,我们可以将很多功能一样的模块文件放到一个文件夹中,Python提出了包(Package)的概念。

什么是包?
从上面的思考简单理解,包就是文件夹,但是区分普通的文件夹,Python包文件夹下必须存放一个名为__init__.py的文件。

包的简单操作

创建包

我们只需要创建一个文件夹,然后在文件夹里面创建一个__init__.py文件就行了,我们就可以把封装好的模块放到该目录下面即可。

包的结构如下:

package_name
├── __init__.py
├── module1.py
├── module2.py
└── sub_package_name
    ├── __init__.py
    └── module3.py

补充说明:包的下面还可以有子包,也可以没有,根据需求来规划包的结构,不一定是上面那种结构。也可以创建普通的文件夹,包的标识在于该文件夹下面是否存在__init__.py文件。

导入包

包的导入和模块导入是一样的,使用importfrom...import导入即可,但是需要注意的是包的导入起点就应该是包名,而不是模块名
【示例】导入一个包下的模块

import 包名.模块名
from 包名.模块名 import 类或方法名
from 包名 import 模块名

温馨提示:在导入的时候不一定要导入到模块层次,也可以只导入包,在使用的时候,去调用包下的哪个模块,只是在你很明确使用包下哪个模块的时候可以导入加深层次,这样在写代码的时候能够更简洁。

了解包的概念之后,Python中对于项目的管理层次就很明显了。层次如下:
包(package文件夹)——>模块(.py文件)——>类(class)——>函数(function)

__init__.py文件

__init__.py文件的作用:

  • 1.标识目录是一个包:Python2中,必须有__init__.py文件才会被识别为包,Python3中可以没有必须拥有,但是依旧会需要该文件保持兼容和可控性。
  • 2.控制包被导入时的行为:加入在包被导入时需要执行的代码块。
  • 3.定义包的对外接口:定义__all__属性,或者定义一些需要暴露的变量和方法对象。

我们可以在__init__.py中放一些初始化变量或者方法。
【示例】代码如下:

# package/__init__.py
name = 'felix'
age = 18

print('package包被导入...')

我们在其他代码文件中导入包里的变量,如下所示:

from package import name, age
people_name = name
people_age = age