当我想在 Python 中导入库时,有不同的方法。我总是选择最常用的那些,但是当我需要将代码投入生产时,为了让它运行得更快,我总是直接导入函数。例如:
我在开发中时导入
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from pygame.locals import *
生产中的进口
from pandas import read_csv, DataFrame, concat
from numpy import array, zeros
我这样做是为了两件事:
- 当我执行函数时,我明白调用函数时调用速度更快
- 在启动时导入一个函数比导入整个包要快
这真的让我的代码更优化吗?
快速回答:没有
这只会使代码的可读性降低,并且对您和其他使用它的程序员来说更难维护。除了您可能拥有具有相同功能的库并且一个会覆盖另一个库这一事实之外,这会导致问题。您应该做的是遵循语言和每个库的约定。
我们将详细介绍,为此我们必须知道 Python 如何执行导入。
Python是如何导入的?
总结当 Python 导入库时,它会执行以下操作:
sys.modules
sys.path
寻找模块,如果没有找到它会给出一个ModuleNotFoundError
,如果找到它就进入下一个操作。(这是导入中最复杂的部分,但为了专注于这个问题,我们将这样总结它)types.ModuleType
。sys.module
以名称作为键以及模块所在的位置(将其添加到缓存中)。globals()
能够使用它。需要注意的是,当第一次导入模块时,所有的模块代码都会被执行,并且只执行一次并加载到内存中,而不是每次执行函数时。
Python 与其他语言不同,它在代码启动时(在运行时)执行导入。这不同于例如 C、C++... 在编译时编译和链接模块。
这样就可以推断出没有时间或空间的优化,因为即使你导入一个函数,Python 总是会以相同的方式编译和执行模块并加载它。但是让我们看看不同的导入方式是如何变化的。
导入方式
我们将把以上所有内容总结为加载过程,因此以下将以不同的方式发生:
导入模块
import math
math
如果之前没有加载模块,我们将它加载到内存中。sys.modules
我们用键添加引用math
math
我们的namespace
引用对象中,这样我们就可以调用这个库中的任何函数math
sys.modules
math.
为模块创建别名
import math as py_math
math
如果之前没有加载模块,我们将它加载到内存中。sys.modules
我们用键添加引用math
py_math
我们的namespace
引用对象中,以便我们可以调用该库中的任何函数math
sys.modules
py_math.
从模块中导入函数
import math import sin
(当谈到认为它更有效时,这是最有争议的)math
如果之前没有加载模块,我们将它加载到内存中。sys.modules
我们用键添加引用math
sin
到namespace
引用对象math.sin
中,sys.modules
所以当我们运行sin
python 时,它会调用math.sin
math
我们namespace
将无法使用它的其余功能。导入所有模块函数
from math import *
math
如果之前没有加载模块,我们将它加载到内存中。sys.modules
我们用 key添加对的引用math
。math
我们将在(并且允许我们)中找到的所有符号添加到我们的namespace
.math
的namespace
.正如我们所看到的,Python 总是执行相同的操作,除了它如何将引用放入我们的
namespace
. 换句话说,唯一改变的是你如何调用那个包,或者你如何调用函数。(这意味着,正如我们稍后看到的,时间会发生变化,但它们是荒谬且不重要的)仅此而已。在任何情况下,您都不是“部分导入包”或“每次调用函数时都导入包”或类似的东西。
时间比较
我们将看到导入模块和执行函数时的时间比较。
模块的导入
让我们通过执行代码来检查以上所有内容是否正确(或部分正确)。为此,我们将创建不同的导入,我们将执行 10M 次,5 次重复:
离开:
导入带别名或不带别名的模块完全相同。但与我们的直觉可能告诉我们的相反,直接导入一个函数比导入整个模块慢 5 倍。
这是因为整个模块的导入,它所做的就是直接把整个模块放到我们的
namespace
.模块,将函数带入我们的命名空间。这个额外的步骤是造成时间增加的原因。函数的使用
我们现在将看看函数的使用方式,如果先执行
math.sin(2)
或导入所述函数然后执行是否更快sin(2)
离开:
直接使用函数
sin(2)
比使用math.sin(2)
. 这是有道理的,因为与导入相反的情况发生在我们身上。通过将函数包含在其中,
namespace
它只需要获取并执行它,而当我们将它调用到模块时,Python 在命名空间中拥有模块,并且必须在其中查找函数sin()
结论
到了这里,你可能弄得一团糟。首先我已经说过一切都是一样的,然后我已经表明它是不一样的。真正繁重的事情,是将模块加载到内存中,它总是做同样的事情,无论你做什么,它总是将整个模块加载到内存中(没有部分导入,或者只编译一个函数)。唯一增加或减少时间的是 Python 通过字典在内部进行查找的不同,Python 字典非常非常快。
想想测试,10M 的导入?谁制造了 1000 万次进口?使用一个函数 1000 万次来获得低于 30% 秒的性能是荒谬的。
得出的结论是,Python 有一种统一且真正优化的模块处理方式。
正如我在开头指出的那样,应该做的是遵循语言和不同库中存在的约定,因为如果不这样做,唯一可以实现的就是代码可读性差,更多对于你自己和其他程序员来说,很难维护和理解。