python的模块和包和其他语言_你真的会用Python模块与工具包吗?

​在开发过程中,我们无法把所有代码、资源都放在同一个文件中。因此,模块导入在编码中是很常见的。无论是C++、Java,还是Python、Go。

可以把不同功能、不同模块进行分离,当使用的时候,可以通过import关键字在一个模块中使用另外一个模块提供的能力,这能够大大提升代码的开发效率。

尤其是对于Python这种对于模块、工具包依赖较强的编程语言,这一点更为突出。

模块导入,这一点在Python中最为常见的东西对于很多人来说都不屑一顾,但是,你真的彻底理解Python中的import吗?

我可以断言,绝大多数Python开发人员只是使用,却不知所以然。

本文,就来详细、彻底的介绍一下Python模块导入的使用。

模块与工具包

模块(modules)和工具包(packages)这2个概念首当其冲,经常被Python开发者混为一谈。

虽然,二者有很多相同之处,但是还是存在一定差异。因此,要想彻底理解import,首先就需要理解模块与工具包的异同点。

模块

Python官网对于模块的定义如下:模块具有一个包含任意Python对象的命名空间,通常用作Python代码组织单位的对象。

实际上,一个模块通常对应一个.py文件,模块的真正功能是可以将其导入到其他代码中,并重复使用,例如:

>>> import math

>>> math.pi

3.141592653589793

第一行代码,通过import把math模块导入到代码中,通过math.pi来调用pi这一属性。

这里需要注意,这里写的math.pi不仅是单纯的pi,它还把math充当所有属性保持统一的命名空间。命名空间对于保持代码的可读性和组织性非常有用。

你可以利用 dir来查看命名空间的内容:

>>> import math

>>> dir()

['__annotations__', '__builtins__', ..., 'math']

>>> dir(math)

['__doc__', ..., 'nan', 'pi', 'pow', ...]

除了上述直接导入,我们还可以导入模块下特定的部分:

>>> from math import pi

>>> pi

3.141592653589793

>>> math.pi

NameError: name 'math'isnot defined

请注意,这里对比于前一种方式已经发生了一些转变。这里的pi是放置在全局命名空间内,而不是math的命名空间内。

同样,首先看一下Python官网对工具包的定义:一个Python模块,可以包含子模块或递归地包含子包。从技术上讲,包是具有__path__属性的Python模块。

从定义上可以看出,包仍然是模块。但是,它们还是有一定的区别。

从编码上来讲,Python包需要在目录下创建一个名为__init__.py的文件。

导入模块时,通常不会导入子模块和子包,但是,你可以通过添加__init__.py来将需要导入的子模块和子包囊括进去。

绝对导入与相对导入

from ... import ...这种导入当时在代码中经常会遇到,假如,我们有如下工程:

world/

├── africa/

│ ├── __init__.py

│ └── zimbabwe.py

├── europe/

│ ├── __init__.py

│ ├── greece.py

│ ├── norway.py

│ └── spain.py

└── __init__.py

当想要导入africa时可以这样:

from world import africa

也可以这样:

from . import africa

那么这里面的**点(.)**代表什么含义?

这里的点(.)就是一种相对导入,你可以理解为从当前包中导入africa。

相反,绝对导入语句中,需要明确命名当前包:

from world import africa

在编码过程中,你可以选择绝对导入,也可以选择相对导入。只不过,PEP 8风格指南中,建议使用绝对导入。

Python导入路径

这是一个需要重点理解的问题,很多开发者从接触Python开始就是用PyCharm,它对于导入路径已经进行了默认的配置,因此,开发者很难遇到无法导入的问题。

但是,当切换到VS Code、Sublime这些需要较多自行配置的开发工具之后,会发现无法导入,或者因为导入工具包带来的调用错误问题。

Python是如何找到它要导入的模块和包的?

你可以试着输出sys.path,你会发现输出列表中主要包含如下3个部分的位置:当前脚本目录

PYTHON_PATH环境变量

其他与安装相关的目录

通常情况下,Python将对列表进行从头开发遍历,从每个位置中寻找给定的模块,直到第一个匹配为止。

由于脚本所在目录始终被排在列表的第一位,因此,导入模块时它会首先从当前目录下进行寻找。

所以,一定不要把自己的代码文件名称与工具包重名。

例如,当前目录有一个名为math.py的文件:

# math.py

def double(number):

return2 * number

这时候,你导入可以按照预期工作:

>>> import math

>>> math.double(3.14)

6.28

但是,它已经覆盖了Python自带的math标准库。如果我们误认为导入的是标准math模块,去调用pi、sqrt这些方法,则会报错:

>>> math.pi

Traceback (most recent call last):

File "", line 1, in

AttributeError: module 'math' has no attribute 'pi'

>>> math

因此,为了避免这个问题,一定小心自己开发代码文件的命名。

创建并安装本地工具包

在Python开发过程中,经常会用到pip安装来自PyPI仓库的工具包,它可以用于全局的工程项目。

除了从仓库下载安装工具包,还可以自行在本地创建工具包,并完成安装。

创建本地安装包只需要创建setup.cfg和setup.py两个项目就行:

# setup.cfg

[metadata]

name = local_structure

version = 0.1.0

[options]

packages = structure

# setup.py

import setuptools

setuptools.setup()

这里的版本名称和版本号可以自行选择。

然后,可以执行下方命令,把创建的安装包安装到本地:

$ python -m pip install -e .

导入样式

为了保持代码的可读性和可维护性,PEP 8提出了一些针对模块导入的规则:将导入放在文件的顶部

每个导入要分行

将导入分组:首先是标准库导入,然后是第三方导入,最后是本地应用程序或库导入

在每个组中按字母顺序排序导入

绝对导入优先于相对导入

避免使用通配符导入from module import *

# Standard library imports

import sys

from typing import Dict, List

# Third party imports

import feedparser

import html2text

# Reader imports

from reader import URL

资源导入

除了模块和工具包,代码开发过程中,还会经常用到外部资源包,针对外部资源包的导入,可以使用importlib.resources。

它是Python 3.7中的标准模块,使用它有2点好处:使得导入方式更加一致

可以更轻松地访问其他包中的资源文件

例如,

>>> from importlib import resources

>>> with resources.open_text("books", "alice_in_wonderland.txt") as fid:

... alice = fid.readlines()

动态导入

Python是一门动态语言,这也是它的主要特点之一。

动态语言使得你可以在程序运行的时候做很多事情,可以添加属性、重新定义方法、更改模块的文档字符串。

例如,通过修改print()函数,使它不做任何操作:

>>> print("Hello dynamic world!")

Hello dynamic world!

>>> # Redefine the built-in print()

>>> print = lambda *args, **kwargs: None

>>> print("Hush, everybody!")

>>> # Nothing is printed

在上述示例中,print函数就被匿名函数重新定义了。

除了这种方法,还有更为易用的动态导入方式,就是利用importlib。

先来看一段示例,

# docreader.py

import importlib

module_name = input("Name of module? ")

module = importlib.import_module(module_name)

print(module.__doc__)

import_module()返回可以绑定到任何变量的模块对象。然后,您可以将该变量视为常规导入的模块。

$ python docreader.py

Name of module? math

This module is always available. It provides access to the

mathematical functions defined by the C standard.

$ python docreader.py

Name of module? csv

CSV parsing and writing.

在每种情况下,该模块都是通过动态导入的import_module()。

周期性导入

如果两个或者多个模块互相导入时,就会发生周期性导入。

例如,有两个模块yin.py和yang.py:

# yin.py

print(f"Hello from yin")

import yang

print(f"Goodbye from yin")

# yang.py

print(f"Hello from yang")

import yin

print(f"Goodbye from yang")

尝试在交互式命令行下导入yin时会发生下面情况:

>>> import yin

Hello from yin

Hello from yang

Goodbye from yang

Goodbye from yin

有些同学会疑惑,这样互相导入,难道不会无限循环下去吗?

这得益于Python的模块缓存机制,在导入yin之后,会首先把它加入到缓存中,后续再导入,会先去参考缓存区域,避免无限循环。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值