可能是史上最细的python中import详解

以前在使用import的时候经常会因为模块的导入而出现一些问题,以及一些似懂非懂半疑惑半糊涂的问题,索性花了点时间研究了一些python引用的方法,并且动手操作试验了一下,深有感触,特留此文以作总结,如有不当之处欢迎评论指正

本文会尽我所能详细描述,字数会比较多,希望各位耐心看完。

首先我觉得应该先了解一下python的引用是怎么引用的

我们首先新建一个python文件demo.py

print(dir())

dir()命令是获取到一个object的所有属性,当传值为空时默认传入的是当前py文件,我们可以通过这个函数来查看py文件的所有属性

['__annotations__', '__builtins__', '__cached__', '__doc__',
'__file__', '__loader__', '__name__', '__package__', '__spec__']

双下划线开头结尾的变量是python中一类特殊的变量,称为python的魔法函数,这里简单解释一些常见属性的意义

  • __annotations__:预定义的变量类型
  • __doc__:文档注释,仅包括第一个''' '''内部的注释文档
  • __file__:文件名,根据被引用还是被执行返回绝对路径还是相对路径
  • __name_:文件名,被执行文件该属性为__main__,被引用文件则返回包名

当我们在python文件中写入一些代码后

a = 1
class b:
    def __init__(self) -> None:
        self.value = "123"
c = [1,2,3]

def f(a,b):
    return a+b
print(dir())

再次执行后可以发现

['__annotations__', '__builtins__', '__cached__', '__doc__',
 '__file__', '__loader__', '__name__', '__package__', '__spec__', 
'a', 'b', 'c', 'f']

相较于之前,所有的变量、类、函数都被加入py文件的属性值中了,也正是如此才能在之后使用这些已经声明的变量或者函数。

对于以下比较常见的库文件引入方式

import math
import torch.nn as nn
from numpy import arange
print(dir())

同理我们可以比较清晰的看到,当引入了一个python库文件的时候其实也就是在python文件中加入了这个库名字,如果是用 as 关键字进行重命名在文件中也会以重命名之后的名字来做保留

['__annotations__', '__builtins__', '__cached__', '__doc__',
 '__file__', '__loader__', '__name__', '__package__', '__spec__',
 'math','nn','arange']

这时我突然产生了一个疑问,那我们使用的print,dir这两个函数又是在哪里定义的呢,我似乎并没有引用任何的库就直接使用了呢。

这其实是一个相当有趣的问题,不过我们先稍等一下,在后面我会回答这个问题。

我们再来带入一个实际的场景,通常一个较为复杂的项目是多文件协同工作的,其中不乏为了命名空间的统一一致性,为了整体文件结构的清晰有序等多种目的而使用多级目录来合理划分文件关系,梳理整体代码架构。

我们引入如下的文件系统环境(此示意图会在文中适当的位置重复出现以加强记忆)

|—— python
|    |—— demo.py
|    |—— folder1
|        |—— a.py
|        |—— b.py
|    |—— folder2
|        |—— c.py
|        |—— d.py

并且每一个a/b/c/d.py文件中分别定义了f1-f4函数以供调用,示意如下:

#a.py
def f1():
    print("this is function f1 in a.py")

然后我们在demo.py中执行以下代码,很显然正确引入没有问题

#demo.py
from folder1.a import f1
from folder1.b import f2
from folder2.c import f3
from folder2.d import f4

f1()
f2()
f3()
f4()

# 输出:
# this is function f1 in a.py
# this is function f2 in b.py
# this is function f3 in c.py
# this is function f4 in d.py

如果我在a.py中想使用b.py中的f2,我也可以更改并执行,没有任何问题

from b import f2

def f1():
    print("this is function f1 in a.py")

f2()

# 输出:
# this is function f2 in b.py

但如果我想在a.py中使用c.py中的f3显然需要一些别的手段,因为这涉及到了跨文件夹的引用

|—— python
|    |—— demo.py
|    |—— folder1
|        |—— a.py
|        |—— b.py
|    |—— folder2
|        |—— c.py
|        |—— d.py

考虑到文件结构层次,a.py位于目录folder1下,我们希望a.py能够回到上一级目录python下,这样就能再进入folder2/c.py顺利引用了。

很多文件也都是这样做的,加入了一个import sys,sys.path,sys.path.append(".")然后问题似乎就顺利解决了,

import sys
sys.path.append(".")

from folder2.c import f3

def f1():
    print("this is function f1 in a.py")

f3()

# 输出:
# this is function f3 in c.py

不过这种做法为什么可行呢,我们不妨来探究一下这种做法正确执行背后的逻辑

首先我们了解一下sys.path有什么作用,我们在demo.py中执行

import sys
print(sys.path)

这里我使用的python环境是Anaconda3创建的一个python3.7虚拟环境,环境名称为fastreid

['g:\\learner_lu\\code-grammar\\python',
 'C:\\ProgramData\\Anaconda3\\envs\\fastreid\\python37.zip',
 'C:\\ProgramData\\Anaconda3\\envs\\fastreid\\DLLs',
 'C:\\ProgramData\\Anaconda3\\envs\\fastreid\\lib', 
 'C:\\ProgramData\\Anaconda3\\envs\\fastreid', 
 'C:\\Users\\Administrator\\AppData\\Roaming\\Python\\Python37\\site-packages',
 'C:\\ProgramData\\Anaconda3\\envs\\fastreid\\lib\\site-packages']

我们可以观察到sys.path中包含了许多绝对路径,第一个路径似乎是demo.py的所在的文件夹,其他的路径配置过环境变量的的小伙伴想必会觉得很眼熟。

而如果我们选择执行a.py,我们会得到以下结果:

['g:\\learner_lu\\code-grammar\\python\\folder1',
 'C:\\ProgramData\\Anaconda3\\envs\\fastreid\\python37.zip',
 'C:\\ProgramData\\Anaconda3\\envs\\fastreid\\DLLs',
 'C:\\ProgramData\\Anaconda3\\envs\\fastreid\\lib',
 'C:\\ProgramData\\Anaconda3\\envs\\fastreid',
 'C:\\Users\\Administrator\\AppData\\Roaming\\Python\\Python37\\site-packages',
 'C:\\ProgramData\\Anaconda3\\envs\\fastreid\\lib\\site-packages']

唯一的区别就是第一个,也印证了我们的猜想,sys.path中的第一个值是被执行的py文件所在文件夹在操作系统中的绝对路径。

那么现在问题来了,其余的路径是什么呢?

  • C:\\ProgramData\\Anaconda3\\envs\\fastreid\\python37.zip是python的压缩包,解压之后就会被删除,路径无效
  • C:\\ProgramData\\Anaconda3\\envs\\fastreid\\DLLs中是所有的.pyd格式的文件,是一种D语言的加密格式,该格式可以被引用但是不能被查看源代码。
  • C:\\ProgramData\\Anaconda3\\envs\\fastreid\\lib中是python自带的一些库,随python一起安装,可以看到我们常见的一些copy.py,glob.py,io.py,os.py
  • C:\\ProgramData\\Anaconda3\\envs\\fastreid是python解释器python.exe所在的目录 ,也是整个py文件被执行时需要启动用来逐行解释语句的文件
  • 剩下两个site-packages则分别是使用pip install / conda install时包的安装位置, 相信对于使用python的小伙伴们来说下载第三方库的操作并不陌生

好了,回到刚才的问题,sys.path中是 被执行的py文件所在文件夹在操作系统中的绝对路径以及使用的python环境下所有的库的目录

|—— python
|    |—— demo.py
|    |—— folder1
|        |—— a.py
|        |—— b.py
|    |—— folder2
|        |—— c.py
|        |—— d.py

到这里已经解决了我的一些疑惑

为什么能 a.py 中能import b ?原来是g:\\learner_lu\\code-grammar\\python\\folder1目录下能找到这个文件

为什么能import os?原来是 C:\\ProgramData\\Anaconda3\\envs\\fastreid\\lib目录下有os.py

为什么能import numpy? 原来是放在xxx\\site-packages下了

所以我们可以总结一下,只要是在sys.path中的路径下能被找到的文件,我们就可以直接使用import引用。

那么之前的问题就很好解释了,a.py的sys.path下找不到c.py,所以我们需要在sys.path中加入能找到c.py的路径,有以下两种方法

#method-1
import sys
sys.path.append('g:\\learner_lu\\code-grammar\\python\\folder2')
import c
c.f3()

# 输出:
# this is function f3 in c.py

#method-2
import sys
sys.path.append('g:\\learner_lu\\code-grammar\\python')
import folder2.c
folder2.c.f3()

# 输出:
# this is function f3 in c.py

虽然可以执行不过显然这种方式比较麻烦,因为你还需要输入目录的绝对路径,这显然不是一个简便的做法。

那么一开始介绍时所使用的sys.path.append(".")又是什么意思呢 ?

|—— python
|    |—— demo.py
|    |—— folder1
|        |—— a.py
|        |—— b.py
|    |—— folder2
|        |—— c.py
|        |—— d.py

.表示的是当前目录,也就是现在你所在的目录。比如我现在处于G:\learner_lu\code-grammar\python,我想要执行a.py可以通过相对路径访问,python folder1/a.py, 而sys.path.append(".") 就是加入了当前所在的G:\learner_lu\code-grammar\python路径,与sys.path.append("G:\learner_lu\code-grammar\python")完全一致。

但如果我现在更进一步,我现在处于G:\learner_lu\code-grammar\python\folder1,那么我想直接运行a.py则可以直接python a.py,这时候的sys.path.append(".")相当于sys.path.append("G:\learner_lu\code-grammar\python\folder1")

也就是说,sys.path.append(".")其实就是在sys.path中加入当前所在的目录的绝对路径,会随着你cd进入或者退出到其他目录而发生改变。

这确实是一种解决办法,省去了繁琐的绝对路径的输入而把当前目录(通常是整个项目最外层的根目录)加入,这样可以直接在文件中引入任意文件的位置。这种做法在许多项目中也都有应用。

#当前目录:G:\learner_lu\code-grammar\python
import sys
sys.path.append('.')
from folder2.c import f3

f3()

# 输出:
# this is function f3 in c.py

使用这种做法时一定要注意你所在的目录最好是根目录,这样能引用到所有文件,如果不在也可以通过sys.path.append('..')等方式得到一个更大的引用范围

还有一种引用的用法也十分常见,就是文件之间通过相对引用.,..,....等文件、函数相互引用,比如还是之前的文件结构

|—— python
|    |—— demo.py
|    |—— folder1
|        |—— a.py
|        |—— b.py
|    |—— folder2
|        |—— c.py
|        |—— d.py

我重写了a.py的f1函数,它调用了来自b.py的f2以及c.py中的f3, 然后我想在demo.py中使用f1函数,这种情况比较常见,那我们应该怎么做呢?

首先我改写a.py,采用相对引用导入f2,f3

from .b import f2
from ..folder2.c import f3

def f1():
    print("start f1")
    f2()
    f3()
    print("end f1")

其次我改写demo.py

from folder1.a import f1
f1()

看起来合情合理,但是运行demo.py时却报错了。

Traceback (most recent call last):
  File "g:\learner_lu\code-grammar\python\demo.py", line 3, in <module>
    from folder1.a import f1
  File "g:\learner_lu\code-grammar\python\folder1\a.py", line 4, in <module>
    from ..folder2.c import f3
ValueError: attempted relative import beyond top-level package

报错原因是“在顶级包之外进行相对引用”,说的云里雾里没太明白什么意思。

那我们运行a.py来先试试看a.py有没有问题?

from .b import f2
from ..folder2.c import f3

def f1():
    print("start f1")
    f2()
    f3()
    print("end f1")

f2()
f3()

这时候更奇怪的事情发生了,报错的位置甚至提前了,第一次至少from .b import f2没有报错,现在居然也报错了?

Traceback (most recent call last):
  File "g:\learner_lu\code-grammar\python\folder1\a.py", line 3, in <module>
    from .b import f2
ImportError: attempted relative import with no known parent package

报错原因是“尝试在没有已知父包的情况下进行相对导入”,明明我用from b import f2来导入没有问题呀,a.py和b.py同目录我导入加一个同目录的.又有什么问题呢 ?这些报错又都是什么意思呢?

这时我们不妨先停一下,我们先来分析一下python是如何进行相对引用的导入模块的。

通过开头的dir() 函数我们可以看到每一个py文件都有一个__name__属性

  • 当这个文件被执行时,这个属性的值为"__main__ "
  • 当它作为模块被引用时,它的属性的值是当前执行的文件到它的相对路径

|—— python
|    |—— demo.py
|    |—— folder1
|        |—— a.py
|        |—— b.py
|    |—— folder2
|        |—— c.py
|        |—— d.py

还是原来的文件目录,我们输出不同情况下c.py的__name__属性

#demo.py
from folder2.c import f3
-------------------------------------------
#d.py
from c import f3
-------------------------------------------
#c.py
print(__name__)
def f3():
    print("this is function f3 in c.py")

# 执行demo.py
# 输出:
# folder2.c

# 执行d.py
# 输出:
# c

# 执行c.py
# 输出:
# __main__

而相对引用的原理也就在此,根据模块的__name__值和使用的.,..等相对路径来搜索文件。

所以说回到刚才的问题,为什么a.py和b.py同目录下使用from .b import f2的相对引用失败了呢?

同目录没问题,相对引用使用.b也没有问题,问题出现在当你执行a.py时,它的包名,也就是__name__ 的值是"__main__",无法通过“__main__"来找到其他文件。也就是说如果a.py并不直接运行,而只是以模块的方式和b.py进行相对引用是没有问题的,例如:

#a.py
from .b import f2
def f1():
    print("this is function f1 in a.py")
-------------------------------------------
#b.py
def f2():
    print("this is function f2 in b.py")
-------------------------------------------
#demo.py
from folder1.a import f1,f2

f1()
f2()

# 执行demo.py
# 输出:
# this is function f1 in a.py
# this is function f2 in b.py

因为这是a.py现在没有被执行,它的的__name__属性是被执行的demo.py到a.py的相对路径,也就是folder1.a,而我们执行demo.py时首先加入sys.path中的就是 demo.py所在的文件夹,也就是G:\learner_lu\code-grammar\python,它使用.b进行相对引用也就是先寻找一个folder1的文件夹,在其中再试图找到一个b.py的文件,可以找到该文件,所以正确执行

那如果我就是想通过相对引来引用b.py并且执行a.py呢?那么依照原理,我们首先更改a的"__name__" 属性,其次我们还需要能找到folder1这个文件夹,因为执行a.py是在sys.path中加入的是 G:\learner_lu\code-grammar\python\folder1它无法找到自己的.,方法如下

#a.py
__name__ = "folder1.a"
import sys
# 当前目录是/python
sys.path.append(".")
from .b import f2
def f1():
    print("this is function f1 in a.py")

f2()

正确执行没有问题,多说一句,其中__name__的值不一定非要是"folder1.a",因为反正是取a的同级目录,那么a的包名是什么其实无关紧要,改为"folder1.asd","folder1.pql"等都可以,但是只能有一个.,多个会被认为是多层的目录结构,寻找的位置就错误了。

不过这种直接改写name的方式图一乐也就好了, 能直接引用完全没必要画蛇添足

所以回到刚才的第二个报错信息,使用from .b import f2时报错,“尝试在没有已知父包的情况下进行相对导入”:

就是因为你是直接运行的a.py,它的包名__name__值是__main__,你又怎么能通过它来找相对引用的文件的位置呢? 而我们运行demo.py时这条引用没有报错是因为这时候a.py的__name__值为folder1.a,可以通过相对引用关系找到folder.b。

然后是刚才的另一个问题,我运行的是demo.py,a.py/b.py/c.py之间通过包名采用相互引用问题在哪里呢?看起来他们的__name__似乎没什么问题啊?

from .b import f2
from ..folder2.c import f3

def f1():
    print("start f1")
    f2()
    f3()
    print("end f1")

demo.py直接引用a没有问题,问题在于a.py中from ..folder2.c import f3

|—— python
|    |—— demo.py
|    |—— folder1
|        |—— a.py
|        |—— b.py
|    |—— folder2
|        |—— c.py
|        |—— d.py

运行 demo.py时a.py的包名为folder1.a,你试图通过..来进入其上级目录,其所在目录folder1的上级目录是python,而执行demo.py时首先加入sys.path的是G:\learner_lu\code-grammar\python目录无法通过其找到同级目录python,需要更高一级目录引用。

所以其报错信息:“在顶级包之外进行相对引用”,就是说现在a.py的包名不够我搜索到它的上级目录,folder1.a仅仅够我执行.,如果你想执行..那么你的包名至少要是python.folder1.a或者更长

有两种解决方法:

1.提高demo.py的目录等级,和python处于同一级下,即文件系统更改为

|—— demo.py
|—— python
|    |—— folder1
|        |—— a.py
|        |—— b.py
|    |—— folder2
|        |—— c.py
|        |—— d.py

在demo.py中使用from python.folder1.a import f1,提高demo.py位置相当于扩大a包的名字,由原来的folder1.a变为现在python.folder1.a,可以使用..来跳至python目录搜索folder2

2.原目录不变,更改a.py引用c.py的间接引用为直接引用

#a.py
from .b import f2
from folder2.c import f3
def f1():
    print("this is function f1 in a.py")
    f2()
    f3()
    print("end of f1")

demo.py不变

from folder1.a import f1
f1()

# 输出:
# this is function f1 in a.py
# this is function f2 in b.py
# this is function f3 in c.py
# end of f1

我在初学python的时候就被告诉说python的引用多级目录之间就直接用.就可以了,这就是python的语法,并没有思考它背后的原理。

其实我们使用.来连接目录与目录,目录与文件,最后写出来的import python.folder1.a其实就是这个文件的包名,python也就是通过查找包名来引入模块的。

现在可以来回答一下开头提出的问题,print函数在哪里被引用了呢?我似乎并没有引入任何模块就直接使用了呢。

python中有一些函数叫做内置函数,观察开头所提到的py文件的属性,有一个属性叫做'__builtins__',这个属性包含了所有内置函数,我们可以通过print(dir(__builtins__))进一步查看

['ArithmeticError', 'AssertionError', 'AttributeError'.......,
abs,any,chr,help,set,round,sum,tuple,list,zip,min,max,str....
print,next,object,pow,quit,dir,.....]

众多我们或熟悉或陌生的内置函数,这些函数并不像其他的py文件需要被导入,python是用c语言来实现的,这些内置函数也是。你可以在[这里](https://hg.python.org/cpython/file/937fa81500e2/Python/bltinmodule.c)找到所有内置函数的代码实现,在[这里](https://hg.python.org/cpython/file/937fa81500e2/Python/bltinmodule.c#l1567)找到print函数的源代码实现,他们是用c语言完成的,他们引用的头文件也都可以在C:\ProgramData\Anaconda3\envs\fastreid\include中到找到对应的源代码,例如code.h,eval.h,但是没有对应的c源文件,本身已经被编译链接成python.exe文件了。关于cpython和python的关系见文末。

当我们使用一个函数时,python首先会查找它是否在该py文件中被引入了,如果没有找到那么就会到内置函数中去查找,再找不到就会报错。

那如果对于模块引用又是怎么查找的呢,优先级是怎么样的呢?

比如我们可以定义一个print函数,他就会覆盖内置函数print被调用

def print(x):
    return 

print("123")

#无输出

现在我们在c.py的同目录下创建一个copy.py文件,那我们引用import copy时引用的是我定义的copy.py还是python环境中的'C:\\ProgramData\\Anaconda3\\envs\\fastreid\\lib'下的copy.py呢?

现在的目录结构如下:

|—— python
|    |—— demo.py
|    |—— folder1
|        |—— a.py
|        |—— b.py
|    |—— folder2
|        |—— c.py
|        |—— copy.py
|        |—— d.py

我在copy.py中定义一个函数deepcopy,它的作用仅仅输出一句话

#copy.py
def deepcopy(x):
    print(f"this is just a deepcopy function of {x} in copy.py")

而原'C:\\ProgramData\\Anaconda3\\envs\\fastreid\\lib'中的copy.py中的同名函数deepcopy的作用是对变量进行深拷贝,全新的复制了一份变量,他们具有不同的地址,相同的值。

现在我在c.py中import copy,那么它会引用哪个呢?

#c.py
import copy

copy.deepcopy("123")

# 输出:
# this is just a deepcopy function of 123 in copy.py

可以看到引用了同目录下的copy.py而不是python环境中的。

这似乎说明了引用的原则 : 优先引用执行文件的目录下的文件。

这时我又产生了疑问,那如果我像之前一样,把copy.py的目录加入sys.path中,不在同目录下引用也是一样的么?

|—— python
|    |—— demo.py
|    |—— folder1
|        |—— a.py
|        |—— b.py
|    |—— folder2
|        |—— c.py
|        |—— copy.py
|        |—— d.py

我在demo.py中把folder2 的目录路径加入sys.path,直接使用import copy引用

#demo.py
#当前目录/python

import sys
sys.path.append("./folder2")
import copy
copy.deepcopy("123")

# 无输出

很奇怪,居然没有输出,这说明它调用的是python环境中的copy.py对“123”进行了深拷贝。

哦我发现了,我使用的是sys.path.append,在列表结尾加入,也就是说现在的sys.path是这样的

['g:\\learner_lu\\code-grammar\\python',
 'C:\\ProgramData\\Anaconda3\\envs\\fastreid\\python37.zip',
 'C:\\ProgramData\\Anaconda3\\envs\\fastreid\\DLLs',
 'C:\\ProgramData\\Anaconda3\\envs\\fastreid\\lib',
 'C:\\ProgramData\\Anaconda3\\envs\\fastreid',
 'C:\\Users\\Administrator\\AppData\\Roaming\\Python\\Python37\\site-packages',
 'C:\\ProgramData\\Anaconda3\\envs\\fastreid\\lib\\site-packages',
 './folder2']

那我要是在列表的头部插入呢?也就是把sys.path变为

['./folder2',
 'g:\\learner_lu\\code-grammar\\python',
 'C:\\ProgramData\\Anaconda3\\envs\\fastreid\\python37.zip',
 'C:\\ProgramData\\Anaconda3\\envs\\fastreid\\DLLs',
 'C:\\ProgramData\\Anaconda3\\envs\\fastreid\\lib',
 'C:\\ProgramData\\Anaconda3\\envs\\fastreid',
 'C:\\Users\\Administrator\\AppData\\Roaming\\Python\\Python37\\site-packages',
 'C:\\ProgramData\\Anaconda3\\envs\\fastreid\\lib\\site-packages']

#demo.py
#当前目录 /python

import sys
sys.path.insert(0,"./folder2")
import copy
copy.deepcopy("123")

# 输出
# this is just a deepcopy function of 123 in copy.py

它又引用了我们定义的copy.py了 !

似乎我们离真相更近了,它是按照sys.path的顺序来搜索的,如果找到了就不会继续往下搜索了,也就是返回第一个找到的结果

现在我们验证一下猜想,我们再尝试一个引用一个math

|—— python
|    |—— demo.py
|    |—— folder1
|        |—— a.py
|        |—— b.py
|    |—— folder2
|        |—— c.py
|        |—— copy.py
|        |—— d.py
|        |—— math.py

新建文件math.py,定义变量pi="123",然后在c.py中引用!

#math.py
pi = "123"

-------------------------------
#c.py
from math import pi

print(pi)

# 输出:
# 3.141592653589793

咦?为什么还是原先的值,不是在同目录下我应该被优先引用么?为什么copy.py行你又犯病了?

经过仔细检查才发现,原来math是一个内置的库,与内置函数类似,也是由c语言编写。

import sys
print(sys.builtin_module_names)

# 输出:
('_abc', '_ast', '_bisect', '_blake2', '_codecs',....
 'math', 'mmap', 'msvcrt', 'nt', 'parser', 'sys', 'time'...)

我们使用的sys,time,math等模块都是内置库,他们需要被显式的通过import xxx引用,并且他们的引用的优先级要高于所有的其他的模块,也就是sys.path中文件只有在与内置库文件不同名是才会被引用,否则引用的一定是内置库模块

现在引用的顺序已经呼之欲出了

  • 首先检查是否是内置库,即在sys.builtin_module_names中搜索,返回第一个找到的结果
  • 其次顺序在sys.path中搜索,排在前面的优先被找到,返回第一个找到的结果

这就需要注意文件的命名规范以及函数作用域等等,除函数重载外尽量避免使用相同的命名,否则后引入的会覆盖先引入的,例如:

#a.py
def f():
    print("this is function f in a.py")
----------------------------------------
#b.py
def f():
    print("this is function f in b.py")
----------------------------------------
#demo.py
from folder1.a import f
from folder1.b import f
f()

# 输出:
# this is function f in b.py

谁后引入使用哪个,这也警示我们在自己编写代码时一定要注意文件、函数、变量的命名规范,一旦错引乱引重载覆盖等都是不易察觉的。

我们又会发现,在demo.py中一个一个引入似乎有些太麻烦了,如果demo2、demo3、demo4都需要引入那大段大段地引入,哪怕是复制粘贴显然也不是我们想要的。能不能有方便一点的方式来引入模块呢?

我们可以在folder1下新建一个__init__.py文件,目录结构如下:

|—— python
|    |—— demo.py
|    |—— folder1
|        |—— __init__.py
|        |—— a.py
|        |—— b.py
|    |—— folder2
|        |—— c.py
|        |—— d.py

如今python3中已不需要显式的定义__init__.py文件来将某文件夹作为包来使用,不过我们可以在其中加入一些东西以方便我们使用,加入如下代码

#__init__.py
from .a import f1
from .b import f2

这样我们可以直接在demo.py中引用

from folder1 import *

f1()
f2()

# 输出
# this is function f1 in a.py
# this is function f2 in b.py

这里的__init__.py的作用就是在引用folder1 时会优先执行这个初始化文件,把folder1作为包来将其下所有的引用集合起来,这样只需要from folder1 import *就可以引用所有的定义了。

除此之外还能在一些文件中遇到__all__这种变量,其实我们在使用from xxx import *的时候就是调用了__all__这个变量,它默认包含所有子文件,属性是一个字符串组成的列表。当然我们可以显式的定义这个变量以达到一些效果,比如:

|—— python
|    |—— demo.py
|    |—— folder1
|        |—— __init__.py
|        |—— a.py
|        |—— b.py
-

我们在a.py下加入几个函数

#a.py
def f1():
    print("this is function f1 in a.py")

def fx():
    print("this is function fx in a.py")

def fy():
    print("this is function fy in a.py")

使用dir()查看demo.py中引入的变量

#__init__.py
from .a import *
from .b import *

#demo.py
from folder1 import *
print(dir())

# 输出
# ['__annotations__', '__builtins__', '__cached__', '__doc__',
# '__file__', '__loader__', '__name__', '__package__', '__spec__',
#  'a', 'b', 'f1', 'f2', 'fx', 'fy']

可以看到__init__.py调用了a,b, 又继续import * 调用a.py b.py中所有的函数 ,所有函数都可以使用

但如果我们在a.py中加入__all__的限制

#a.py
__all__ = ["f1","fy"]

def f1():
    print("this is function f1 in a.py")

def fx():
    print("this is function fx in a.py")

def fy():
    print("this is function fy in a.py")
-------------------------------------------
#demo.py
from folder1 import *
print(dir())

# 输出
# ['__annotations__', '__builtins__', '__cached__', '__doc__',
# '__file__', '__loader__', '__name__', '__package__', '__spec__',
#  'a', 'b', 'f1', 'f2', 'fy']

fx这个函数就会被抛弃,不会被引用。这种方式通常被用来剔除当前文件中的一些私有基类以及基函数,不希望被引用。

类似的我们也可以在__init__.py中使用

from .a import *
from .b import *

__all__ = ["fx","b"]

#demo.py
from folder1 import *
print(dir())

# 输出
# ['__annotations__', '__builtins__', '__cached__', '__doc__',
# '__file__', '__loader__', '__name__', '__package__', '__spec__',
#  'b', 'fx']

这样在demo.py中只能使用约束在__all__中的函数或变量,可以手动区分命名空间,防止污染

需要注意的是,只有在使用from xxx import *这种引用方式时才涉及到__all__,其他的引用方式设置此变量都对引用无影响。

总结

到此这篇关于python的import详解就介绍到这了,最后祝大家天天进步!!,更多相关python中import详解内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Python import自定义模块方法

    python包含子目录中的模块方法比较简单,关键是能够在sys.path里面找到通向模块文件的路径. 下面将具体介绍几种常用情况: (1)主程序与模块程序在同一目录下: 如下面程序结构: `-- src |-- mod1.py `-- test1.py 若在程序test1.py中导入模块mod1, 则直接使用import mod1或from mod1 import *; (2)主程序所在目录是模块所在目录的父(或祖辈)目录 如下面程序结构: `-- src |-- mod1.py |-- mod

  • Python中标准模块importlib详解

    1 模块简介 Python提供了importlib包作为标准库的一部分.目的就是提供Python中import语句的实现(以及__import__函数).另外,importlib允许程序员创建他们自定义的对象,可用于引入过程(也称为importer). 什么是imp? 另外有一个叫做imp的模块,它提供给Python import语句机制的接口.这个模块在Python 3.4中被否决,目的就是为了只使用importlib. 这个模块有些复杂,因此我们在这篇博文中主要讨论以下几个主题: •动态引入

  • python之import机制详解

    本文详述了Python的import机制,对于理解Python的运行机制很有帮助! 1.标准import: Python中所有加载到内存的模块都放在 sys.modules .当 import 一个模块时首先会在这个列表中查找是否已经加载了此模块,如果加载了则只是将模块的名字加入到正在调用 import 的模块的 Local 名字空间中.如果没有加载则从 sys.path 目录中按照模块名称查找模块文件,模块可以是py.pyc.pyd,找到后将模块载入内存,并加到 sys.modules 中,并

  • Python中import机制详解

    Python语言中import的使用很简单,直接使用 import module_name 语句导入即可.这里我主要写一下"import"的本质. Python官方 定义:Python code in one module gains access to the code in another module by the process of importing it. 1.定义: 模块(module):用来从逻辑(实现一个功能)上组织Python代码(变量.函数.类),本质就是*.p

  • Python引用(import)文件夹下的py文件的方法

    Python的import包含文件功能就跟PHP的include类似,但更确切的说应该更像是PHP中的require,因为Python里的import只要目标不存在就报错程序无法往下执行.要包含目录里的文件,PHP中只需要给对路径就OK.Python中则不同,下面来看看这个例子. 目录结构: a.py 要 import dir目录下的 b.py 文件.a.py代码如下: 复制代码 代码如下: # coding=utf-8 "import dir 目录下的 b.py 文件"   impo

  • Python import与from import使用及区别介绍

    下面介绍下Python import与from import使用,具体内容如下所示: Python程序可以调用一组基本的函数(即内建函数),比如print().input()和len()等函数.Python本身也内置一组模块(即标准库).每个模块都是一个Python程序,且包含了一组相关的函数,可以嵌入到你的程序之中,比如,math模块包含了数学运算相关的函数,random模块包含随机数相关的函数,等等. 一.import语句 在开始使用一个模块中的函数之前,必须用import语句导入该模块.

  • 详解Python import方法引入模块的实例

    详解Python import方法引入模块的实例 在Python用import或者from-import或者from-import-as-来导入相应的模块,作用和使用方法与C语言的include头文件类似.其实就是引入某些成熟的函数库和成熟的方法,避免重复造轮子,提高开发速度. python的import方法可以引入系统的模块,也可以引入我们自己写好的共用模块,这点和PHP非常相似,但是它们的具体细节还不是很一样.因为php是在引入的时候指明引入文件的具体路径,而python中不能够写文件路径进

  • Python import用法以及与from...import的区别

    在python用import或者from...import来导入相应的模块.模块其实就是一些函数和类的集合文件,它能实现一些相应的功能,当我们需要使用这些功能的时候,直接把相应的模块导入到我们的程序中,我们就可以使用了.这类似于C语言中的include头文件,Python中我们用import导入我们需要的模块. eg: 复制代码 代码如下: import sys print('================Python import mode==========================

  • 跟老齐学Python之Import 模块

    认识模块 对于模块,在前面的一些举例中,已经涉及到了,比如曾经有过:import random (获取随机数模块).为了能够对模块有一个清晰的了解,首先要看看什么模块,这里选取官方文档中对它的定义: 复制代码 代码如下: A module is a file containing Python definitions and statements. The file name is the module name with the suffix .py appended. Within a mo

  • 可能是史上最细的python中import详解

    以前在使用import的时候经常会因为模块的导入而出现一些问题,以及一些似懂非懂半疑惑半糊涂的问题,索性花了点时间研究了一些python引用的方法,并且动手操作试验了一下,深有感触,特留此文以作总结,如有不当之处欢迎评论指正 本文会尽我所能详细描述,字数会比较多,希望各位耐心看完. 首先我觉得应该先了解一下python的引用是怎么引用的 我们首先新建一个python文件demo.py print(dir()) dir()命令是获取到一个object的所有属性,当传值为空时默认传入的是当前py文件

  • windows上安装Anaconda和python的教程详解

    一提到数字图像处理编程,可能大多数人就会想到matlab,但matlab也有自身的缺点: 1.不开源,价格贵 2.软件容量大.一般3G以上,高版本甚至达5G以上. 3.只能做研究,不易转化成软件. 因此,我们这里使用Python这个脚本语言来进行数字图像处理. 要使用Python,必须先安装python,一般是2.7版本以上,不管是在windows系统,还是Linux系统,安装都是非常简单的. 要使用python进行各种开发和科学计算,还需要安装对应的包.这和matlab非常相似,只是matla

  • 史上最全Android build.gradle配置详解(小结)

    Android Studio是采用gradle来构建项目的,gradle是基于groovy语言的,如果只是用它构建普通Android项目的话,是可以不去学groovy的.当我们创建一个Android项目时会包含两个Android build.gradle配置详解文件,如下图: 一.Project的build.gradle文件: 对应的build.gradle代码如下: // Top-level build file where you can add configuration options

  • Python中正则表达式详解

    基础篇 正则表达式在python中运用的非常多,因为他可以进行任意的匹配,可以匹配我们想要提取的信息.当我们接触正则的时候你就会知道正则的强大.正则有一个库re 在一些工程中我们会经常调用正则的库来做与匹配相关的问题. 字符串是编程时涉及到的最多的一种数据结构,对字符串进行操作的需求几乎无处不在.比如判断一个字符串是否是合法的Email地址,虽然可以编程提取 @ 前后的子串,再分别判断是否是单词和域名,但这样做不但麻烦,而且代码难以复用. 正则表达式是一种用来匹配字符串的强有力的武器.它的设计思

  • Python海龟绘图详解

    目录 一.画笔和屏幕 二.实战与应用 1.写字 2.用于交互式写字 3.自己控制方向 4.拖动小海龟画图 5.规定时间后开始画 hello,大家好,我是Dream.最近有小伙伴私信我,说让我出一篇海龟画图,这其实我也不太能弄得明白,那在这里我和大家一块梳理一下!记得给我点赞收藏哟~ 一.画笔和屏幕 这里单独拿开一个讲的话会很抽象,因为一个是线,另一个是面,面是线的载体,线是面的内容 首先大家先运行这一段代码: import turtle#调用turtle库 turtle.title("绘制第一只

  • ChatGPT教你用Python实现BinarySearchTree详解

    目录 前言 ChatGPT 截图 对 ChatGPT 的一些感悟 1.ChatGPT 成为下一代搜索引擎,毋庸置疑 2.上下文关联能力强 3.未来的可能性 前言 至今,ChatGPT 已经火了很多轮,我在第一轮的时候注册了账号,遗憾的是,没有彻头彻尾好好地体验过一次.最近这一次火爆,ChatGPT 确实出圈了,各行各业的人们都在晒,趁着周末,我也小试了一把. 这篇文会介绍我使用 ChatGPT 的过程以及使用时的一些感悟,最后浅谈下我对 ChatGPT 的一些理解,不喜勿喷哈~ 我的问题是:我是

  • Python网络编程详解

    1.服务器就是一系列硬件或软件,为一个或多个客户端(服务的用户)提供所需的"服务".它存在唯一目的就是等待客户端的请求,并响应它们(提供服务),然后等待更多请求. 2.客户端/服务器架构既可以应用于计算机硬件,也可以应用于计算机软件. 3.在服务器响应客户端之前,首先会创建一个通信节点,它能够使服务器监听请求. 一.套接字:通信端点 1.套接字 套接字是计算机网络数据结构,它体现了上节中所描述的"通信端点"的概念.在任何类型的通信开始之前,网络应用程序必须创建套接字

  • python中import reload __import__的区别详解

    import 作用:导入/引入一个python标准模块,其中包括.py文件.带有__init__.py文件的目录(自定义模块). import module_name[,module1,...] from module import *|child[,child1,...] 注意:多次重复使用import语句时,不会重新加载被指定的模块,只是把对该模块的内存地址给引用到本地变量环境. 实例: pythontab.py #!/usr/bin/env python #encoding: utf-8

  • Linux上安装Mysql及简单的使用详解

    1. 安装mysql sudo apt-get update sudo apt-get install mysql-server sudo apt-get install python-mysqldb(如果python中要使用,请安装) 2.登录root用户: mysql -u root -p 3.查看所有数据库: show databases; 4.选择一个数据库操作: use database_name; 5.查看当前数据库下所有的表: show tables; 6.创建一个数据库: cre

  • [机器视觉]使用python自动识别验证码详解

    前言 CAPTCHA全称Completely Automated Public Turing Test to Tell Computers and Humans Apart,即全自动区分人机的图灵测试.这也是验证码诞生的主要任务.但是随着近年来大数据运算和机器视觉的发展,用机器视觉识别图像已经变得非常容易,过去用于区分人机的验证码也开始变得不再安全. 接下来就让我们从零开始,深入图像处理和算法构建,来看看使用机器视觉来识别过时的验证码( 如下所示 )究竟可以有多简单. 载入需要的程序包 & 设置

随机推荐