Python中令人困惑的模块导入

Python中令人困惑的模块导入

一句话总结: 绝对路径保平安
相关文件见此

问题描述

我在过去很长一段使用Python的时间里, 都仅限于一些简单模块, 文件结构也都很简单, 文件嵌套不会超过2层, 所以即便在模块导入上碰到些Module Not Found的问题, 也都是出问题那会儿去网上搜下方法赶紧应付过去----直到现在.

比如说我当前有个项目, 里面我有两个功能需要实现, 功能1和功能2可以独立运行, 但最终会搭配使用. 我将其分别放在该项目的两个文件夹下, 各自作为一个独立模块, 在这每个文件夹内, 包含了一些为了该模块服务的子模块(或者说子文件夹), 类似于:

─ my_project├── main.py├── module_1│   ├── __init__.py│   ├── data│   ├── enum_type│   └── module_1.py└── module_2├── __init__.py├── data├── enum_type└── module_2.py

子模块之间可能会互相调用, 于是我在开发过程中频繁地出现找不到模块的问题, 比如好不容易调试好了modue_1/enum_type下的某个枚举文件, 然后在module_1.py中导入时发现找不到模组, 而在好不容易完成module_1的功能, 并在module_2/module_2.py中尝试调用时, 又告诉我找不到module_1模块的位置…

鉴于解决模块路径花费过多时间, 所以决定至少从这次起, 要找到一个稳妥且绝大多数时刻都适用的导入方式, 以免以后又在这种踩了不知多少次的坑上继续耗费时间.

本文测试环境以Python3.10为主, 但适用于Python3的任意版本.

本文流水账记事, 且水得会比较厉害, 可直接跳转至总结部分.

什么是脚本(Script)

现在当你尝试学习Python时, 在配置环境后, 按照当前大部分的教程, 基本都是手动或在IDE内新建一个hello.py文件, 键入经典的问候代码, 点击IDE(VSCode, PyCharm等)的运行按钮或在命令台里通过python3 hello.py即可完成程序的运行. 但我们(或者说只是我自己)也许忘了, 最开始的hello代码是在Shell(终端)内, 通过Python解释器运行的. 就是那个在命令行界面内输入python3后弹出的界面:

# Python解释器:
neowell@Vault:~/project/confusing_import$ python3
Python 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> print('hello world!')
hello world!
>>>

但用这种方式运行的代码, 在你退出解释器后就会丢失1, 下次进入解释器时就得重新输入代码. 方便起见, 会考虑先将代码保存在一个文件里, 这样当你需要执行时直接使用这个文件中的内容就好了. 这内容就是所谓的脚本(Script).

此处的"脚本"指的是你实际编写的代码内容, 而不是用于保存代码的xxx.py文件本身.

什么是模块(Module)

通过编写脚本, 除了可防止代码运行后丢失外, 还有一个好处, 就是可以复用, 当多个程序都需要调用同一个函数时, 你不必将这段函数代码复制到每个程序中, 只用通过语句声明来调用同一个文件就好.

那么像这样的一个保存了特定脚本内容、可被其它程序调用, 或者说导入的文件, 就被称作模块(Module).
一个模块文件通过后缀 .py 被Python识别. 我们平时写的各种.py文件其实可以看作是一个个模块.

演示-1

现在我们来看一个简单例子, 为了使我们的hello world代码在退出解释器后仍能被使用, 我们创建一个hello_module.py文件用于保存以下代码:

# hello_module.py
def say_hello() -> None:print('Hello world!')

现在我们通过终端重新进入Python解释器, 通过import关键字来调用该模块并使用模块的say_hello函数, 请留意执行时的路径:

# 1. 列出当前文件
neowell@Vault:~/project/confusing_import/simple_case$ ls
hello_module.py# 2. 查看hello_module.py的内容
neowell@Vault:~/project/confusing_import/simple_case$ cat hello_module.py
# hello_module.py
def say_hello() -> None:print('Hello world!')# 3. 在解释其中导入模块并调用其中的方法
neowell@Vault:~/project/confusing_import/simple_case$ python3
Python 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import hello_module
>>> hello_module.say_hello() # 通过module.xx的方式访问模块中的特定函数
Hello world!
>>>

甚至可以在一段程序里重复调用:

neowell@Vault:~/project/confusing_import/simple_case$ python3
Python 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import hello_module
>>> hello_module.say_hello()
Hello world!
>>> hello_module.say_hello()
Hello world!
>>> hello_module.say_hello()
Hello world!
>>>

看, 这样一来我们不用在解释器内重新编写代码, 只用导入提前写好的模块就可以了, 也不用担心退出解释器后代码的丢失问题了.

除了import module的写法, 你也可用通过from module import function的方式只导入特定的函数:

neowell@Vault:~/project/confusing_import/simple_case$ python3
Python 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from hello_module import say_hello
>>> say_hello()
Hello world!
>>>

演示-2

这样做的另一个好处则在于, 通过模块导入来使用其中函数的方式, 可以避免两个模块中出现同名函数时, 发生调用冲突的问题. module_1.hello()module_2.hello()得以通过模块名区分.

关于每个模块的名字, 我们可以通过全局变量__name__来取得:

neowell@Vault:~/project/confusing_import/simple_case$ python3
Python 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import hello_module
>>> hello_module.__name__
'hello_module'
>>>

你可以放心地在另一个模块中同样编写一个say_hello函数, 并在同一个程序中调用:

# howdy_module.py
def say_hello() -> None:"""greeting with another form"""print(f'Howdy world!')
# 两个不同的问候模块
neowell@Vault:~/project/confusing_import/simple_case$ ls
hello_module.py  howdy_module.py# 调用不同模块的同名方法
neowell@Vault:~/project/confusing_import/simple_case$ python3
Python 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import hello_module
>>> import howdy_module
>>> hello_module.say_hello()
Hello world!
>>> howdy_module.say_hello()
Howdy world!
>>>

你也可以通过as为模块或函数赋予一个别名来进一步简化调用方式:

neowell@Vault:~/project/confusing_import/simple_case$ python3
Python 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import hello_module as hm
>>> from howdy_module import say_hello as say_hello
>>> hm.say_hello()
Hello world!
>>> say_hello()
Howdy world!
>>>

演示-3

当通过import导入模块时, 会执行模块内的代码, 这也是上述的演示代码中, 要将目标代码以函数的形式封装的原因. 如果不这么做, 而是直接将指定代码写入文件的话:

# naive_module.py
print('Hello world from naive module!')

那么在导入模块的那一步, 就会直接执行模块中的代码, 这种擅自执行的情况显然不是我们想看见的:

neowell@Vault:~/project/confusing_import/simple_case$ python3
Python 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import naive_module
Hello world from naive module!
>>>

我们可以利用这个特性来简化自己程序的一些初始化操作, 这一特点将在后文阐述. 但除此以外, 我们在编写模块时, 应当避免这样的状况, 将所需的功能代码封装起来.

什么是包(Package)

我们需要通过编写脚本的形式管理我们的代码, 并加以复用, 这个成品文件就是模块. 同样地, 我们在完成一个个模块文件后, 也需要管理这些文件.

以我们先前的代码举例, 我们用英文输出了一句问候语"Hello world", 现在我们拓展一下, 要求用不同语言来输出这句问候语, 进一步, 不仅仅是问候语, 我们要求按语种, 分别输出各自语言里表示道歉、感谢等通用词语, 为此你需要有诸如apology.py, thank.py等, 现在我们又增加了语种要求, 所以你的实际文件名可能诸如chinese_hello.py, english_hello.py, chinese_apology.py, english_apology.py

你当然可以像这样一把梭, 全部放在一起. 不过为了更好地管理与今后方便拓展, 这时我们就需要包(Package)了. 简单来说, 包可以看作我们日常在电脑里为了分类创建的各种文件夹, 比如在上述这个例子里, 我们如果以包的思路去组织文件, 它的形式大概如下:

world_module/
├── __init__.py # 将world_module初始化为一个包
├── chinese
│   ├── __init__.py # 将chinese初始化为一个子包
│   ├── apology.py
│   ├── hello.py
│   └── thank.py
└── english├── __init__.py # 将english初始化为一个子包├── apology.py├── hello.py└── thank.py

整体结构与使用文件夹分类无异. 通过在每个文件夹下创建一个__init__.py文件, 这样才可以让Python将这些文件夹视作可调用的包. 我们通过两个hello.py文件来看下效果:

# 1. 此处只关注world_module文件夹
neowell@Vault:~/project/confusing_import$ ls
day_of_life  my_project  simple_case  world_module# 2. 查看两个语言模块下的问候子模块
neowell@Vault:~/project/confusing_import$ cat world_module/chinese/hello.py
def say_hello() -> None:print(f'你好, 世界!')
neowell@Vault:~/project/confusing_import$ cat world_module/english/hello.py
def say_hello() -> None:print('Hello World!')# 3. 分别调用它们
neowell@Vault:~/project/confusing_import$ python3
Python 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import world_module.chinese.hello as c_hello
>>> import world_module.english.hello as e_hello
>>> c_hello.say_hello()
你好, 世界!
>>> e_hello.say_hello()
Hello World!
>>>

通过包的形式, 方便模块的统一与拓展, 而且在语义上也更容易理解模块的作用.

关于__init__.py, 将在后面一章来解释其主要作用, 当前只用记得一点: 当你使用文件夹管理模块文件时, 请在这个文件夹下创建一个空的__init__.py文件(包括文件夹内的嵌套文件夹), 使Python将这个文件夹标记为一个包.

Python文件的运行方式

脚本式运行

在上一章中, 通过编写脚本, 将其保存为一个.py文件, 便可将其作为一个模块供其它程序调用. 除此以外, 我们也可以直接运行编写的脚本文件本身, 格式为python3 xx.py.这也是我们大部分人所熟知的方式.

演示-1

同样以上一章的例子hello_module.py, 为了使代码运行, 在定义函数后对其调用:

# hello_module.py
# 定义函数
def say_hello() -> None:print('Hello world!')# 调用函数
say_hello()

运行结果:

neowell@Vault:~/project/confusing_import/simple_case$ python3 hello_module.py
Hello world!
演示-2

在上一章末尾的naive_module例子提到过, 我们应当避免在文件内直接放入可执行代码, 为此, 我们可以使用上一章提到的全局变量 __name__. 当我们调用模块时, 通过module.__name__可以获取模块的模块名, 而当我们通过脚本式直接运行模块文件时, 会把 __name__ 的值赋值为 “__main__”, 我们可以先改写上述的文件来测试下:

# hello_module.py
# 定义函数
def say_hello() -> None:print('Hello world!')# 调用函数
say_hello()# 查看当前的__name__
print(f'__name__ = {__name__}')

运行结果:

neowell@Vault:~/project/confusing_import/simple_case$ python3 hello_module.py
Hello world!
__name__ = __main__

基于此原理, 我们可以在模块文件中, 通过判断 __name__ 的值来决定:

# hello_module.py
# 定义函数
def say_hello() -> None:print('Hello world!')if __name__ == '__main__':say_hello()

这样无论以模块被调用, 还是以脚本式运行, 都可以根据这个if条件来分情况执行:

  • 以脚本运行:
neowell@Vault:~/project/confusing_import/simple_case$ python3 hello_module.py
Hello world!
  • 以模块调用:
neowell@Vault:~/project/confusing_import/simple_case$ python3
Python 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import hello_module
>>> hello_module.say_hello()
Hello world!
>>>

这是Python中运行代码的推荐方式, 如果你有其它语言基础, 会发现这像其它语言中的main函数概念, 这样也能让你的代码层次更清晰, 在查看别人的代码时, 你也容易知道该从哪里入手.

模块的搜索方式

本章可能需要你对Python的虚拟环境有基本的了解或使用经验, virtualenv, conda, poetry等皆可. 至少需要了解虚拟环境的创建和切换, 以及在不同环境下安装过第三方模块.

当你在脚本中导入外部模块(或包)时, 解释器会从以下这几个地方进行搜索2:

  1. 安装Python时的默认值
  2. PYTHONPATH环境变量
  3. 运行脚本时脚本的所在目录

Python的搜索路径可以通过sys.path进行查看, 通过sys.executable可以查看python解释器的路径. 通过这两个方法, 我们可以自行验证当前python环境的模块搜索路径, 以及当处于虚拟环境时, 此时的解释器位置. 这是两个非常有用的命令, 接下来以此我们查看下以上三种方法的运作形式. 请留意每个例子中执行的路径位置.

1. 安装python时的默认值

这是当我们在系统下(无论是主机还是容器中)安装python(无论通过包管理工具、自行编译还是其它方式)时的情况, 我们还是先通过解释器来查看相关内容:

# 查看默认Python环境的包搜索路径, 以及Python的执行路径
neowell@Vault:~/project/confusing_import/simple_case$ python3
Python 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.path
['', '/usr/lib/python310.zip', '/usr/lib/python3.10', '/usr/lib/python3.10/lib-dynload', '/home/neowell/.local/lib/python3.10/site-packages', '/usr/local/lib/python3.10/dist-packages', '/usr/lib/python3/dist-packages']
>>> sys.executable
'/usr/bin/python3'
>>>

当然, 这个部分我们当前可以不用关心, 这些部分一般是我们使用pip安装第三方包时使用的路径. 主要作用在创建虚拟环境时的查看, 请留意创建路径:

# 1. 创建虚拟环境的文件夹, 名为env_sc, 位于simple_case下
neowell@Vault:~/project/confusing_import/simple_case$ python3 -m venv env_sc# 2. 激活虚拟环境
neowell@Vault:~/project/confusing_import/simple_case$ source env_sc/bin/activate# 3. 查看虚拟环境下Python执行路径的变化
(env_sc) neowell@Vault:~/project/confusing_import/simple_case$ python
Python 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.path
['', '/usr/lib/python310.zip', '/usr/lib/python3.10', '/usr/lib/python3.10/lib-dynload', '/home/neowell/project/confusing_import/simple_case/env_sc/lib/python3.10/site-packages']
>>> sys.executable
'/home/neowell/project/confusing_import/simple_case/env_sc/bin/python'
>>>

我在simple_case文件夹里面创建并激活了一个虚拟环境, 通过这种方法创建的python解释器路径就是我创建虚拟环境时对应的路径了.

题外话: 只要你的项目涉及第三方模块的安装时, 你就应该通过虚拟环境工具或docker等容器管理工具创建相互独立的Python开发环境. 或者极端点说, 任何情况下你都不应该在系统默认的Python环境内运行项目, 关于这一点, 在2023年甚至已有老哥想通过PEP704将该想法化为了提案3.

2.PYTHONPATH环境变量

我们前面所有的例子都存放在~/project/confusing_import/simple_case这个文件夹下:

simple_case/
├── env_sc
├── hello_module.py
├── howdy_module.py
├── naive_module.py
└── naked_module.py

当我们在上一级目录, 尝试直接导入模块, 这样当然会失败:

# 1. 返回上一级目录
neowell@Vault:~/project/confusing_import/simple_case$ cd ..# 2. 尝试在该路径下导入模块
neowell@Vault:~/project/confusing_import$ python3
Python 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import hello_module
Traceback (most recent call last):File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'hello_module'
>>>

我们可以通过为PYTHONPATH添加该路径的方式, 使Python解释器搜索时会检索我们添加的路径位置:

# 1.查看当前的PYTHONPATH值, 当前为空
neowell@Vault:~/project/confusing_import$ echo $PYTHONPATH# 2. 将simple_case的路径加入
neowell@Vault:~/project/confusing_import$ export PYTHONPATH=/home/neowell/project/confusing_import/simple_case
# 3. 再次确认
neowell@Vault:~/project/confusing_import$ echo $PYTHONPATH
:/home/neowell/project/confusing_import/simple_case# 4. 再次尝试导入模块
neowell@Vault:~/project/confusing_import$ python3
Python 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import hello_module
>>> hello_module.say_hello()
Hello world!
>>> sys.path
['', '/home/neowell/project/confusing_import/simple_case', '/usr/lib/python310.zip', '/usr/lib/python3.10', '/usr/lib/python3.10/lib-dynload', '/home/neowell/.local/lib/python3.10/site-packages', '/usr/local/lib/python3.10/dist-packages', '/usr/lib/python3/dist-packages']
>>>

通过这种形式, 成功找到了我们所需的模块位置. 通过sys.path会发现PYTHONPATH的路径也被添加进搜索路径

通过export设置环境变量的方法是临时性的, 在关闭当前窗口后便会丢失. 欲永久性地添加环境变量, 请自行搜索, 本处不作展开.

3. 运行脚本时脚本的所在目录

终端里所处路径无论在哪里, Python的搜索路径都以脚本的位置为默认搜索路径:

# 在simple_case文件夹下执行脚本:
(env_sc) neowell@Vault:~/project/confusing_import/simple_case$ python hello_module.py
/home/neowell/project/confusing_import/simple_case
/usr/lib/python310.zip
/usr/lib/python3.10
/usr/lib/python3.10/lib-dynload
/home/neowell/project/confusing_import/simple_case/env_sc/lib/python3.10/site-packages
Hello world!# 返回上级目录并执行脚本:
(env_sc) neowell@Vault:~/project/confusing_import/simple_case$ cd ..
(env_sc) neowell@Vault:~/project/confusing_import$ python simple_case/hello_module.py
/home/neowell/project/confusing_import/simple_case
/usr/lib/python310.zip
/usr/lib/python3.10
/usr/lib/python3.10/lib-dynload
/home/neowell/project/confusing_import/simple_case/env_sc/lib/python3.10/site-packages
Hello world!

所以当你的脚本中依赖其它自己编写的其它模块时, 需保证那个模块的路径与脚本路径处在同一位置, 不然就会出现找不到模块的问题.

模块式运行

在上一节中, 当我们按脚本运行时, Python会将脚本所在的路径加入到搜索路径中, 那么有没有一种方法将其反过来, 以我们执行时的路径作为搜索路径呢? 这就是本章的作用.

对于一个保存了脚本内容的xx.py, 为脚本运行命令添加一个-m的标记, 使Python以模块的方式运行该文件: python -m xx. 由于此处是按模块运行, 所以你需要去除.py的文件后缀.
如果你的模块以包的形式管理:

xx/
├── __init__.py
└── yy.py

调用时的命令就为: python -m xx.yy.

演示

我们首先为hello_module.py增加一个打印__name__的方法, 并在以脚本运行时自动打印__name___:

# hello_module.py
def say_hello() -> None:print('Hello world!')def get_name() -> None:print(f'当前命名为{__name__}')if __name__ == '__main__':import sysfor i in sys.path:print(i)say_hello()get_name()

此时的simple_case文件夹如下, 通过创建__init__.py将其变为一个包:

simple_case/
├── __init__.py (标记该文件夹为一个包)
├── env_sc (虚拟环境)
├── hello_module.py (主要文件)
├── howdy_module.py
├── naive_module.py
└── naked_module.py
  • 首先还是在simple_case文件夹下按脚本与模块方式分别运行:
# 1. 按脚本运行
(env_sc) neowell@Vault:~/project/confusing_import/simple_case$ python hello_module.py
/home/neowell/project/confusing_import/simple_case
/usr/lib/python310.zip
/usr/lib/python3.10
/usr/lib/python3.10/lib-dynload
/home/neowell/project/confusing_import/simple_case/env_sc/lib/python3.10/site-packages
Hello world!
当前命名为__main__# 2. 按模块运行
(env_sc) neowell@Vault:~/project/confusing_import/simple_case$ python -m hello_module
/home/neowell/project/confusing_import/simple_case
/usr/lib/python310.zip
/usr/lib/python3.10
/usr/lib/python3.10/lib-dynload
/home/neowell/project/confusing_import/simple_case/env_sc/lib/python3.10/site-packages
Hello world!
当前命名为__main__

此时两者的搜索路径同为/simple_case

  • 接下来我们返回上级目录, 再看下结果:
# 1. 返回上级目录
(env_sc) neowell@Vault:~/project/confusing_import/simple_case$ cd ..# 2. 按脚本运行
(env_sc) neowell@Vault:~/project/confusing_import$ python simple_case/hello_module.py
/home/neowell/project/confusing_import/simple_case # <======注意此处
/usr/lib/python310.zip
/usr/lib/python3.10
/usr/lib/python3.10/lib-dynload
/home/neowell/project/confusing_import/simple_case/env_sc/lib/python3.10/site-packages
Hello world!
当前命名为__main__# 3. 按模块运行
(env_sc) neowell@Vault:~/project/confusing_import$ python -m simple_case.hello_module
/home/neowell/project/confusing_import # <======注意此处
/usr/lib/python310.zip
/usr/lib/python3.10
/usr/lib/python3.10/lib-dynload
/home/neowell/project/confusing_import/simple_case/env_sc/lib/python3.10/site-packages
Hello world!
当前命名为__main__
(env_sc) neowell@Vault:~/project/confusing_import$

此时我们发现, 当按模块方式执行时, 搜索路径就根据你当前所处的路径为主了.

模块导入方式的对比

现在回到我们最初问题描述部分的例子, 并为了举例方便, 为模块1添加一个枚举模块:

─ my_project├── main.py├── module_1│   ├── __init__.py│   ├── data│   ├── enum_type│   │   └── __init__.py│   │   └── m1_enum.py│   └── module_1.py└── module_2├── __init__.py├── data├── enum_type└── module_2.py

我会把主要的逻辑放在main.py中, 其中会使用到module_1module_2的模块moduel_1.pymodule_2.py, 这些模块它们也会依赖各自的子模块, 比如存放数据的data模块, 管理枚举类的enum_type模块.

当我首先编写模块1module_1.py, 而该模块恰好需要一个枚举类, 为了方便, 我会让自己处于my_project/module_1/路径下, 这样我可以方便地使用脚本调用的方式来测试其中的模块:python module_1.py. 此时该模块其中的代码大概是这样的:

# m1_enum.py
def enum_call() -> None:print(f'This is enum class')
# module_1.py
from enum_type.m1_enum import enum_call
def module_1():enum_call()
if __name__ == '__main__':module_1()

此时测试没问题, 我的模块1非常完美:

# 注意此时所处路径
(env_sc) neowell@Vault:~/project/confusing_import/my_project/module_1$ ls
__init__.py  data  enum_type  module_1.py
(env_sc) neowell@Vault:~/project/confusing_import/my_project/module_1$ python module_1.py
This is enum class

OK, 既然模块1的功能已经搞定, 而我最终要在主目录下的main.py中调用, 于是我先尝试引入模块1来测试下, 此时的主方法:

import module_1.module_1 as mod_1def main() -> None:mod_1.module_1()if __name__ == '__main__':main()

测试看看:

(env_sc) neowell@Vault:~/project/confusing_import/my_project$ python main.py
Traceback (most recent call last):File "/home/neowell/project/confusing_import/my_project/main.py", line 1, in <module>import module_1.module_1 as mod_1File "/home/neowell/project/confusing_import/my_project/module_1/module_1.py", line 1, in <module>from  enum_type.m1_enum import enum_call
ModuleNotFoundError: No module named 'enum_type'

发生报错, 当代码执行到module_1.py中时, 系统告诉我无法找到enum_type这个模块. 之前对模块1的测试没有问题, 此处为何报错了呢? 原因就出在了搜索路径上:

  1. 当测试模块1时, 由于是按脚本运行, 所以那时搜索路径为my_project/module_1, 这时enum_type处于该路径下, 就没有问题;
  2. 而当测试主函数时, 搜索路径变为了my_project/, 在这个路径下找不到模块1的枚举包, 自然发生了报错.

为了解决该问题, 我们要做的就是将enum_type添加到Python的搜索路径中, 比如:

  1. 将对应路径添加到PYTOHNPATH中;
  2. enum_type移动到my_project/下;
  3. 在代码中通过sys.path.append添加相关路径;
  4. 更改module_1.py中的导入方式, 将其按照main.py的位置进行修改;

第一种方法需要你自己手动将路径添加, 当模块众多时, 你可能还得编写一个额外的脚本来进行批量添加操作;第二种显然不行, 虽然有效, 但会完全弄乱文件结构, 第三种方法和第一种类似, 仍需要靠自己手动添加, 而且在文件头添加这样的代码也不大利于阅读, 当然, 这不是什么大问题.

就我个人而言, 当前会推荐第四种方法. 即把项目的根目录当作起点, 使其中所有的模块调用全部遵循绝对路径的导入方式, 并以模块运行的方式来测试其中的每一个模块文件.

首先来修改下本例中的文件:

# module_1.py
from module_1.enum_type.m1_enum import enum_call # <===按根路径导入模块def module_1():enum_call()if __name__ == '__main__':module_1()

这样一来, 主函数可以正常运行, 无论哪种方式:

# 脚本调用
(env_sc) neowell@Vault:~/project/confusing_import/my_project$ python main.py
This is enum class
# 模块调用
(env_sc) neowell@Vault:~/project/confusing_import/my_project$ python -m main
This is enum class

当然这时无法按脚本调用module_1.py了:

(env_sc) neowell@Vault:~/project/confusing_import/my_project$ python module_1/module_1.py
Traceback (most recent call last):File "/home/neowell/project/confusing_import/my_project/module_1/module_1.py", line 1, in <module>from module_1.enum_type.m1_enum import enum_callFile "/home/neowell/project/confusing_import/my_project/module_1/module_1.py", line 1, in <module>from module_1.enum_type.m1_enum import enum_call
ModuleNotFoundError: No module named 'module_1.enum_type'; 'module_1' is not a package

没关系, 我们改为模块调用, 使搜寻路径从根目录开始:

(env_sc) neowell@Vault:~/project/confusing_import/my_project$ python -m module_1.module_1
This is enum class

这么做会繁琐了点, 但我认为好处较为明显:

  1. 不用考虑导入时的路径问题, 统一导入思路, 减少该部分的思维负担和避免外工作量(编写环境变量导入脚本);
  2. 模块的独立化, 比如模块1的功能如果不涉及其它模块的引用, 那么在完成该模块以后, 我完全可以将该模块(文件夹)复制出来丢到另一个项目中, 不用作任何修改就能使用;
  3. 方便测试, 如果随后需要进行单元测试, 你可能还需要在根目录下新建一个test文件夹, 并存放你的测试代码, 通过这种根目录式的导入方式, 你在测试文件夹下的导入也会更加简洁;

通过模块调用也有个比较麻烦的地方, 那就是当你运行命令python -m xx.yy时, 是不支持自动补全的… 这其实在你测试模块时会感到麻烦. 此处我的办法是在测试模块时, 用alias命令绑定测试命令, 从而简化输入, 比如我把qq与模块命令绑定: alias qq="python -m xx.yy". 这样之后只用在命令行里输入qq即可, 而且这个命令也只是临时性的, 关闭当前窗口后就会消失:

(env_sc) neowell@Vault:~/project/confusing_import/my_project$ alias qq="python -m module_1.module_1"
(env_sc) neowell@Vault:~/project/confusing_import/my_project$ qq
This is enum class

模块导入方式总结

不再使用脚本式运行python module.py, 而是基于设置根目录为Python搜索路径的思路, 以绝对路径的导入方式, 使导入结构清晰化, 且可统一在根目录下通过python -m module的方式测试处于不同路径、不同深度的模块文件而不用担心找不到模块的问题.

…就是还有一点, 我们看上面的那个导入代码from module_1.enum_type.m1_enum import enum_call, 这会不会太长了点? 再看那个模块调用的部分module_1.module_1, 前面的module_1是被__init__.py标注为包的文件夹, 后面的module_1是模块文件module_1.py, 这在命名规则上虽然没什么问题, 但在分辨上却容易造成混淆, 如果子模块嵌套深一些, 这个导入可能会变得很长且不好辨认.

为此, 我们可以通过__init__.py这个文件来优化我们在主模块部分的阅读问题.

关于__init__.py

之前的章节中, 我们通过创建一个空的__init__.py来标记一个文件夹为包(Package), 在本章, 我们通过修改该文件, 来提高我们在主模块中遇到的一些体验问题.

首先, 先处理下在之前包的章节中被刻意忽视的一个部分.

无需__init__.py即可导入包?

如果你自己之前实操过, 可能会发现自己并没有创建这个__init__.py文件, 但仍能正常导入:

neowell@Vault:~/project/confusing_import$ ls not_package/
hello_module.py
neowell@Vault:~/project/confusing_import$ cat not_package/hello_module.py
# hello_module.py
def say_hello() -> None:print('Hello world!')def get_name() -> None:print(f'当前命名为{__name__}')if __name__ == '__main__':import sysfor i in sys.path:print(i)say_hello()get_name()neowell@Vault:~/project/confusing_import$ python3
Python 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import not_package.hello_module as hm # <=======未标记的情况下仍能正常导入
>>> hm.say_hello()
Hello world!
>>>

当遇到以上情况, 其中一种可能是你的Python版本大于等于3.3. 这是因为在PEP 4204中, 即从Python3.3开始, 引入了名为(隐式)命名空间包(Implicit Namespace Packages)的机制, 旨在解决处于同一命名空间下不同包的调用问题, 比如:

company/
├── pkg_1
│   └── mod
│       └── a.py
└── pkg_2└── mod└── b.py

简单点说, 通过该机制, 我们可以通过mod.amod.b的形式调用这两个模块. 但也是由于这个机制, 现在所有在项目下创建的文件夹都会被Python自动看作为一个包.

所以在Python3.2及之前的版本, 你仍需要手动创建__init__.py, 而在Python3.3及以后的版本, 你可以不用显式地创建.
在此我也自己通过Docker找了几个对应的镜像验证了下:

Python2.7中的导入

Python2.7应该是Python2时代最后的一个大版本, 且是Python2中的主流版本, 所以此处对Python2的实验版本为2.7.

由于暂时没找到官方的Python2.7镜像, 所以就选择Centos镜像, 其内部预置Python2.7, 我使用的版本为:
docker pull centos:centos7

我们首先看下同样的程序在python2.7下的表现:

[root@0f2acf31a627 simple_case_py2]# ls
hello_module.py[root@0f2acf31a627 simple_case_py2]# cat hello_module.py
# hello_module.py
def say_hello():print('Hello world!')if __name__ == '__main__':say_hello()[root@0f2acf31a627 simple_case_py2]# python
Python 2.7.5 (default, Oct 14 2020, 14:45:30)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-44)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import hello_module
>>> hello_module.say_hello()
Hello world!
>>>

现在我们把hello_module.py移动到当前路径下的一个新文件夹, 并尝试调用:

[root@0f2acf31a627 simple_case_py2]# ls
mod
[root@0f2acf31a627 simple_case_py2]# ls mod/
hello_module.py
[root@0f2acf31a627 simple_case_py2]# python
Python 2.7.5 (default, Oct 14 2020, 14:45:30)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-44)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import mod.hello_module as hm
Traceback (most recent call last):File "<stdin>", line 1, in <module>
ImportError: No module named mod.hello_module

无法找到指定模组, 现在我们在mod文件夹内创建一个空的__init__.py文件, 再尝试调用:

[root@0f2acf31a627 simple_case_py2]# touch mod/__init__.py
[root@0f2acf31a627 simple_case_py2]# python
Python 2.7.5 (default, Oct 14 2020, 14:45:30)
[GCC 4.8.5 20150623 (Red Hat 4.8.5-44)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import mod.hello_module as hm
>>> hm.say_hello()
Hello world!

此时可以成功调用, 通过__init__.py文件, 使mod文件夹被当作一个模组看待

Python3中的导入
  • Python3.2
root@6e1f2113da8a:~# ls
root@6e1f2113da8a:~# mkdir module_1
root@6e1f2113da8a:~# python
Python 3.2.6 (default, Jan 18 2016, 19:21:14)
[GCC 4.9.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import module_1
Traceback (most recent call last):File "<stdin>", line 1, in <module>
ImportError: No module named module_1 # <======Python3.2下, 没有__init__.py的文件夹无法被Python视作包
>>># 创建__init__.py以标记该文件夹为包
root@6e1f2113da8a:~# touch module_1/__init__.py
root@6e1f2113da8a:~# python
Python 3.2.6 (default, Jan 18 2016, 19:21:14)
[GCC 4.9.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import module_1
>>> module_1
<module 'module_1' from 'module_1/__init__.py'>
>>>
  • Python3.3
root@be639363057e:~# ls
root@be639363057e:~# mkdir module_1
root@be639363057e:~# python
Python 3.3.7 (default, Sep 19 2017, 23:13:00)
[GCC 4.9.2] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import module_1 # <=======没有__init__.py依然能将module_1视作包导入
>>> module_1
<module 'module_1' (namespace)>  # <=======此时以隐式命名空间的形式将module_1视为包
>>># 再显式地创建__init__.py, 重新测试
root@be639363057e:~# touch module_1/__init__.py
root@be639363057e:~# python
Python 3.3.7 (default, Sep 19 2017, 23:13:00)
[GCC 4.9.2] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import module_1
>>> module_1
<module 'module_1' from './module_1/__init__.py'> # <=============显式创建下的情况
>>>
显式好于隐式(Explicit is better than implicit)

根据上面的表现, 在没有特别要求, 且使用Python3.3+的情况下, 是否应该省略该文件的创建呢? 这里也装模做样地引用Python之禅(The Zen of Python) 其中的一句话: "显式好于隐式(Explicit is better than implicit)"5. 当你不需要使用命名空间包, 仅仅只是用于标记包时, 就不应该使用该功能, 而是显式创建文件来标记文件夹. 通过这样的标记也能帮助你更好地认识、组织项目结构, 哪些文件夹是包, 哪些文件夹仅仅是文件夹.

包的初始化

好了, 在又水了一章后, 回到__init__.py本身.
当从包里导入对应模块时, 会首先执行__init__.py中的代码:

# __init__.py定义一个简单的打印代码
neowell@Vault:~/project/confusing_import$ ls my_package/
__init__.py  __pycache__  hello_module.py
neowell@Vault:~/project/confusing_import$ cat my_package/__init__.py
print(f'__init__率先执行: {__name__}')# 导入my_package包
Python 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import my_package.hello_module as hm
__init__率先执行: my_package # <======__init__.py内的代码被执行
>>> hm.say_hello()
Hello world!
>>>

如同Python类中的__init__方法, 用于初始化相关参数. __init__.py就好比包的初始化方法.

现在回到上一章的例子(省略掉无用的module_2):

.
├── main.py
└──── module_1├── __init__.py├── data├── enum_type│   ├── __init__.py│   └── m1_enum.py└── module_1.py
  • module_1.py
from module_1.enum_type.m1_enum import enum_calldef module_1():enum_call()if __name__ == '__main__':module_1()
  • main.py
import module_1.module_1 as mod_1def main() -> None:mod_1.module_1()if __name__ == '__main__':main()

现在我们来修改对应代码以使用新的导入语句:

  1. 枚举包的修改/module_1/enum_type/__init__.py:
# 当前m1_enum.py中仅enum_call一个方法
from module_1.enum_type.m1_enum import enum_call
  1. 此时使用了该包的/module_1/module_1.py可简化:
# from module_1.enum_type.m1_enum import enum_call
""" 
当导入module_1.enum_type包时, enum_type/__init__.py首先执行语句:
"from module_1.enum_type.m1_enum import enum_call"
此时enum_call已被导入, 此时在本文件内便可直接调用.除了缩短代码长度, 语义也更清晰, 即该模块从module_1下的枚举包内导入了一个名为enum_call的方法
"""
from module_1.enum_type import enum_calldef module_1():enum_call()if __name__ == '__main__':module_1()
  1. 我们可以先测试下当前的效果, 注意执行路径仍处于根目录下:
neowell@Vault:~/project/confusing_import/my_project$ ls
__pycache__  main.py  module_1  module_2
neowell@Vault:~/project/confusing_import/my_project$ python3 -m module_1.module_1
This is enum class
  1. 接下为了处理main.py, 我们首先配置/module_1/__init__.py:
from module_1.module_1 import module_1

通过该步骤, 我们也正式将模块1封装为一个完整的包

  1. 而对于/main.py:
import module_1.module_1 as mod_1
# from module_1 import module_1 as mod_1 <====也可以这么写def main() -> None:# mod_1.module_1() # <==========需修改此处的调用方式mod_1()if __name__ == '__main__':main()

之前的module_1.module_1代表的是module_1.py这个模块, 所以在调用时需要调用模块中的方法, 名字也是module_1(), 而现在修改后, module_1.module_1代表的就是module_1()这个方法.

  1. 测试, 没问题:
neowell@Vault:~/project/confusing_import/my_project$ python3 -m main
This is enum class

通过__init__.py可以方便地预配置部分参数, 但请避免将需要长时间计算的步骤放在这里面, 不然会导致性能问题.

区分命名空间

在前面模块部分有提到过, 通过不同的模块作为命名, 我们可以编写相同名称方法置于不同的模块里, 而不用担心重名问题. 此处的__init__.py也有这样的作用.

比如对前面的例子, 为了更加正规与管理, 我们在测试时, 其实应当统一放在一个文件夹下, 比如这样:

neowell@Vault:~/project/confusing_import/my_project$ ls test/
__pycache__  test.py
neowell@Vault:~/project/confusing_import/my_project$ cat test/test.py
"""
此处存放单元测试代码, 现在为了简略暂不使用
"""
import module_1.module_1 as mod_1def main_test() -> None:mod_1()if __name__ == '__main__':main_test()

基于前面提到的隐式命名空间, 省略__init__.py是可以的, 这样导入没有问题, 我们现在来测试下:

neowell@Vault:~/project/confusing_import/my_project$ python3 -m test.test
/usr/bin/python3: No module named test.test

发生报错, 无法找到模块. 这里之所以会这样, 是因为在Python中, 也有一个内置的模块就叫做test, 我们可以调用测试下:

neowell@Vault:~/project/confusing_import/my_project$ python3 -m test
== CPython 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0]
== Linux-5.15.133.1-microsoft-standard-WSL2-x86_64-with-glibc2.35 little-endian
== cwd: /tmp/test_python_194133æ
== CPU count: 22
== encodings: locale=UTF-8, FS=utf-8
0:00:00 load avg: 0.32 Run tests sequentially
...

此时我们如果修改test文件夹的名字, 与内置模块区分命名, 就没问题了:

neowell@Vault:~/project/confusing_import/my_project$ mv test/ testXX
neowell@Vault:~/project/confusing_import/my_project$ python3 -m testXX.test
This is enum class

这么做可以解决, 也没有问题. 不过仍需考虑这两点:

  1. test作为测试文件夹命名已属于相对成熟的习惯性约定
  2. 依赖了隐式命名空间的(副作用)机制

为此, 我们可以将test文件夹标记为一个包, 这样仍可以与内置模块区分:

neowell@Vault:~/project/confusing_import/my_project$ mv testXX/ test
neowell@Vault:~/project/confusing_import/my_project$ touch test/__init__.py
neowell@Vault:~/project/confusing_import/my_project$ python3 -m test.test
This is enum class

最后总结

为了避免再在包的导入上耗费不必要的时间, 我选择了一种绝对的方法, 即全部以项目根目录为起点来进行包/模块的导入, 并通过__init__.py对包的初始化功能, 简化了在主模块中的导入方式, 并提高了语义信息, 进一步提升了阅读体验.

尽管不够聪明, 但当前够用了, 等以后踩着新坑或有更多经验了再看看怎么去进一步优化吧.

参考

[1] Python官方中文文档: 模块

[2] Python官方中文文档: 模块搜索路径

[3] Python增强提案 704 - Require virtual environments by default for package installers

[4] Python增强提案 420(已生效) - Implicit Namespace Packages

[5] Python之禅- The Zen of Python(PEP 0020)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/35480.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

itextpdf-PdfStamper写入文件流到ByteArrayOutputStream注意事项

itextpdf-PdfStamper写入文件流到ByteArrayOutputStream注意事项 1、前情提要 最近做项目时有个需求&#xff0c;把图片合成pdf并且加水印&#xff0c;然后通过base64返回到前端。本来想的很简单&#xff0c;使用PdfStamper&#xff0c;把参数FileOutputSteam换成ByteArrayOutp…

简述Java项目中VO,BO,PO,DO,DTO之类的文件概念、易混点

VO&#xff0c;BO&#xff0c;PO&#xff0c;DO&#xff0c;DTO 概念易混点一&#xff1a;VO和DTO- 让我们通过一个实例来阐释DTO和VO的概念及其应用差异&#xff1a;小结&#xff1a;VO专注于展示&#xff0c;而DTO则用于数据的传输和业务逻辑的处理。 二&#xff1a;BO和PO小…

公益培训|半导体与集成电路项目制培训项目

关于我们 硬蛋产业学院&#xff0c;基于硬蛋创新(http://00400.HK)在芯片产业的资源和技术优势&#xff0c;引进全球领先的芯片应用技术&#xff0c;为国内培养芯片应用技术人才&#xff0c;助力芯片应用产业发展。 硬蛋产业学院在国家各主管部门、广东省、深圳市及社会各界的大…

AI 激发算力需求暴增,施耐德电气解码智算中心发展

随着全球碳达峰目标的持续推进&#xff0c;各行各业都在加速绿色转型的步伐&#xff0c;尤其是高耗能产业更是备受关注。人工智能行业以其迅猛的发展速度令人瞩目&#xff0c;它所带来的不仅是算力需求的飙升&#xff0c;更是日益凸显的能耗问题。 目前&#xff0c;人工智能预…

AVI 是什么格式,AVI 格式用什么播放器打开?

AVI 是什么格式&#xff1f;提到 AVI 格式想必大家多数会想到在 DVD 横行的年代&#xff0c;光盘中所包含的媒体视频格式多是以 AVI 格式存储。AVI 是一个非常通用的容器格式&#xff0c;支持多种视频和音频编解码器。这意味着从DVD中提取视频内容时&#xff0c;可以通过转码为…

发电机保护屏产品介绍,组成

发电机保护屏产品介绍&#xff0c;组成 发电机保护屏是用于保护发电机组的电气装置。它根据发电机的类型和实际运行要求&#xff0c;将多种保护装置组合在一起&#xff0c;形成一个保护屏柜。发电机保护测控屏是指把发电机类保护装置集中在安装在一个控制柜里&#xff0c;主要用…

Java程序递归及mybatis递归查询

之前项目组有个需求&#xff0c;定时同步机构的信息。已知三方接口由于返回数据量很大&#xff0c;所以最后需要三方提供一个可根据机构编号获取当前机构及子机构信息的接口。而不是一次性返回全部机构信息&#xff01; 由于这次需求也用到了递归&#xff0c;所以记录下&#…

Python自动化操作:简单、有趣、高效!解放你的工作流程!

今天跟大家分享一套自动化操作流程解决方案&#xff0c;基于Python语言&#xff0c;涉及pyautogui、pyperclip、pythoncom、win32com依赖包。安装命令为&#xff1a; pip install pyautoguipip install pyperclippip install pythoncompip install win32compyautogui 是一个自…

【python入门】循环语句

文章目录 1. for 循环2. while 循环3. break 和 continue 语句4. else 子句5. 循环控制语句6. 列表推导式7. 循环中的异常处理8. 循环的高级用法 1. for 循环 for 循环通常用于遍历序列&#xff08;如列表、元组、字典、集合、字符串&#xff09;或者迭代器。for 循环可以自动…

病理性不对称引导的渐进学习用于急性缺血性脑卒中梗死分割| 文献速递-先进深度学习疾病诊断

Title 题目 Pathological Asymmetry-Guided Progressive Learning for Acute Ischemic Stroke Infarct Segmentation 病理性不对称引导的渐进学习用于急性缺血性脑卒中梗死分割 01 文献速递介绍 中风已经成为第二大致命疾病&#xff0c;大约70%的中风是缺血性的。众所周知…

Kafka官方提供的RoundRobinPartitioner出现奇偶数据不均匀

Kafka官方提供的RoundRobinPartitioner出现奇偶数据不均匀 参考&#xff1a; https://www.cnblogs.com/cbc-onne/p/18140043 使用RoundRobinPartitioner /** Licensed to the Apache Software Foundation (ASF) under one or more* contributor license agreements. See the…

如何提高网页加载速度?

如何以闪电般的速度加载网站&#xff1f; 看看这 8 个提升前端性能的技巧&#xff1a; 01 压缩 在传输之前压缩文件可以减少其大小&#xff0c;减少需要传输的数据量&#xff0c;从而加快加载时间。 实现方法&#xff1a; Gzip/Brotli 压缩: 配置你的 web 服务器&#xff08…

[Linux] 历史根源

UNIX系统&#xff1a; 1969年&#xff0c;由贝尔实验室的K.Thompson和D.M.Ritchie为PDP-7机器编写的一个分时操作系统&#xff0c; 最初使用汇编语言编写&#xff0c; 后来1972年C语言出世以后&#xff0c;二人由使用C写了UNIX3&#xff0c; 此后UNIX大为流行开来 UNIX流派树&a…

一个简单的盐值md5破解

声明&#xff0c;仅供学习&#xff0c;请勿用于非法用途&#xff01; 首先需要获取到salt值和密文&#xff0c;自己有字典 我是做vulhub的cmsms复现的时候&#xff0c;用他的poc跑出来密文和盐值&#xff0c;发现这个是做了字段拼接再加密&#xff0c;也就是加盐了&#xff0c…

华为交换机的堆叠-Stack配置(基于业务口普通线缆的堆叠配置)

不想看原理请跳过一、二、三、四&#xff0c; 直接到配置五&#xff0c;干完活有时间在慢慢看原理。 一、什么是堆叠-Stack 指将多台交换机通过堆叠线缆连接在一起&#xff0c;逻辑上变成一台交换设备&#xff0c;作为一个整体参与数据转发。即&#xff1a;1 1 一 二、堆叠…

ChatGPT 宣布终止对中国提供 API 服务? 来用国产大模型吧

国产大模型 阿里云 将为 OpenAI API 用户提供更具性价比的中国大模型替代方案&#xff0c;同时宣布中国开发者提供 2200 万免费 tokens 和专属迁移服务 硅基流动 SiliconCloud 免费开放 7 款大模型 智谱 AI 正式推出 OpenAI API 用户特别搬家计划&#xff0c;同时还会为开…

如何通过待办工具提升个人效率 减轻压力提升效率的待办app

在快节奏的现代社会中&#xff0c;工作任务繁重&#xff0c;人们的压力日益增大。为了减轻压力并提升工作效率&#xff0c;我们急需找到一种有效的方法来管理日常任务。幸运的是&#xff0c;随着科技的进步&#xff0c;各种新兴工具应运而生&#xff0c;为我们提供了便捷的解决…

qt报错:“QtRunWork”任务返回了 false,但未记录错误。

qt报错&#xff1a;“QtRunWork”任务返回了 false&#xff0c;但未记录错误。 说明情况一 说明 这个报错可能的原因有很多&#xff0c;这里只写一种&#xff0c;以后遇到再进行补充。 情况一 如果 Q_OBJECT 宏未正确处理&#xff0c;通常会出现类似的错误。 要使用信号与槽…

3.优化算法之二分查找1

二分查找简介 1.特点 最简单最恶心&#xff0c;细节最多&#xff0c;最容易写出死循环的算法 2.学习中的侧重点 1&#xff09;算法原理 数组有序的情况 2&#xff09; 模板 不要死记硬背 ->理解之后再记忆 1.朴素的二分模板 2.查找左边界的二分模板 3.查找右边界的二分模板 …

24年了 直播带货的未来如何?

32 个国家在取消电商&#xff0c; 那我国的电商呢&#xff0c;首先电商是不会被取缔的。直播电商会被严格的控制&#xff0c;比如有一家饼店&#xff0c;它线下的销售是 3000 万&#xff0c;线上抖音的销售是 5, 000 万。 这一类型小而精又专业的品牌企业&#xff0c;未来在抖…