从导入的模块加载文件的频率是多少?

时间:2017-07-07 21:50:47

标签: python import

我很好奇文件在从模块中需要时实际加载到内存中的频率。这是场景:

lookup.py

import json

mapping_dict = json.load(open('map.json'))

def map_file(file_name):
    return(mapping_dict[file_name])

def other_function():
    --do something--

main.py

import lookup

--something something--

for f in files:
    print(lookup.map_file(f))

所以我的问题是来自lookup.py的mapping_dict加载了多少次?一旦初始导入?每次调用map_file?每次调用lookup.py中的任何函数时(在这种情况下为map_file和other_function)。

上面的一些组合?

3 个答案:

答案 0 :(得分:0)

每个模块只加载一次。 import语句首先检查模块是否存在于sys.modules中,如果存在,它只是执行字典查找。如果找不到该项,则会加载该模块。您可以使用reload(仅限Python-2)或删除sys.modules中的条目来强制重新导入模块(这有点像黑客攻击)。

In [1]: import sys as s1

In [2]: s1.modules['sys']
Out[2]: <module 'sys' (built-in)>

In [3]: del s1.modules['sys']

In [4]: id(s1)            # different than s2 and s3
Out[4]: 140359411362736

In [5]: import sys as s2  # same as s3

In [6]: id(s2)
Out[6]: 140359175854240

In [7]: s1 == s2
Out[7]: False

In [8]: s2
Out[8]: <module 'sys' (built-in)>

In [9]: import sys as s3

In [10]: id(s3)
Out[10]: 140359175854240

请注意,重新加载给定模块不会重新加载它导入的任何库(仅在下面的Python2中,在Python3中,您可以使用importlib.reload):

>>> import sysconfig
>>> s = reload(sysconfig)
>>> id(sysconfig) == id(s)
False
>>> id(sysconfig.sys) == id(s.sys)
True

答案 1 :(得分:0)

正如评论中所回答的那样,但我会稍微展开一下,以便真正说明原因。

问题是,我们加载JSON文件的次数是多少次(来自if __name__ == 'main'的第3行)

导入mapping_dict时,假设周围没有{{1}}防护,{{1}}(对象)设置为json文件的内容。同样,定义的函数每个都分配给一个函数对象。您可以通过键入最后没有括号的函数名来测试它。您应该看到与对象

对应的内存指针

从那里开始,除非重新导入lookup.py,否则不会重新分配或更新mapping_dict。引用这些函数会引用它们所分配的对象。

答案 2 :(得分:0)

我不确定其他答案是否非常明确,所以让我发一些可能(或可能没有)帮助的其他内容。以下是在正常情况下导入模块时有效发生的事情:

  • Python在sys.modules(基本上是dict)中查找具有要导入的模块名称的条目。如果它找到了某个东西,它将获取该对象,创建一个与模块名称对应的变量,并将该对象分配给变量。
  • 如果在sys.modules中找不到对象,则会创建一个对象来表示该模块,并将其放在sys.modules下的相应名称下。然后它查找包含模块源代码的文件,将该文件作为Python脚本(ish)运行,然后挖掘之后存在的所有变量定义,并将它们指定为新模块对象的属性。

请注意,这意味着文件中的代码仅在首次导入模块时执行一次。

在您的示例中,让我们看看它在实践中是如何工作的。 Python很高兴在main.py中运行代码并遇到

import lookup

“好的(Python说),让我们转到sys.modules并查看是否有任何内容存储在名称lookup下。[...检查...]不,没有任何内容存储在姓名。查找lookup.py的时间(使用sys.path)。“

Python创建一个新的空模块对象,并将其放在名为sys.modules的{​​{1}}下。然后它打开文件lookup并开始运行代码。

lookup.py

Python现在以import json 名称sys.modules查找。它将以递归的方式完成整个过程,但我将跳过详细信息,然后跳到jsonsys.modules下的json下完全加载的模块对象的位置。然后,Python将该对象分配给当前范围中的名称json(实际上,它json = sys.modules['json']}。

mapping_dict = json.load(open('map.json'))

Python现在加载JSON文件。我正在跳过大部分细节,但它涉及访问当前作用域中的json变量(记住,指的是模块对象)并获取该对象的load属性,并且然后叫它。来自该调用的内容将被放入当前范围中的另一个变量mapping_dict

def map_file(file_name):
    return(mapping_dict[file_name])

Python现在看到def并暂时停止执行;相反,它读取一段代码,将其编译为字节码,并将该字节码粘贴到一个函数对象中。该函数对象被分配给当前作用域中的变量map_file。然后,回到执行。

def other_function():
    --do something--

再一次,Python停止执行并读取一段代码,将其编译成字节码,并将该字节码粘贴到一个函数对象中,该函数对象被赋值给变量other_function

现在我们位于文件lookup.py的末尾。我们在当前范围内有四个变量:jsonmapping_dictmap_fileother_function。 Python将这四个变量指定为模块对象的属性。请记住,模块对象存储在密钥sys.modules下的lookup中,因此现在sys.modules['lookup']是一个包含四个属性jsonmapping_dict,{{ 1}}和map_file

回到other_function的时间,我们仍在完成main.py声明。该语句的最后一件事是将import lookup的值赋给当前作用域sys.modules['lookup']中的变量。 (实际上,lookup。)同样,该对象有四个属性,如前所述。

继续

lookup = sys.modules['lookup']

在每次迭代循环中,Python访问for f in files: print(lookup.map_file(f)) 变量,该变量引用模块对象,然后访问该对象的lookup属性,该属性基本上只包含一个一堆字节码。 Python然后调用函数对象,它实际上执行字节码 - 记住,这是map_file定义中的代码,def map_file(...)定义时{em>不执行}模块已加载。直到现在它才被执行。

所有代码都是访问lookup并返回它。 mapping_dict[file_name]只是一个引用现有对象的变量;大概是mapping_dict。因此,访问dict只需检查mapping_dict[file_name],看看它是否有一个带有与dict值对应的键的条目,如果是,则返回它。访问此条目不会打开任何文件或执行任何复杂的操作。特别是,它运行file_name,因为该位代码不是函数的一部分。这段代码很久以前就被运行了,导入了json.load(open('map.json')),并且没有保存以便再次运行,所以在正常情况下它不会再运行。