以前在使用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']
唯一的