#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 *
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Python工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Python开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Python开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注Python)
最后
不知道你们用的什么环境,我一般都是用的Python3.6环境和pycharm解释器,没有软件,或者没有资料,没人解答问题,都可以免费领取(包括今天的代码),过几天我还会做个视频教程出来,有需要也可以领取~
给大家准备的学习资料包括但不限于:
Python 环境、pycharm编辑器/永久激活/翻译插件
python 零基础视频教程
Python 界面开发实战教程
Python 爬虫实战教程
Python 数据分析实战教程
python 游戏开发实战教程
Python 电子书100本
Python 学习路线规划
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
43c1008edf79.png)
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Python开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注Python)
[外链图片转存中…(img-Jws5FrVy-1712711313113)]
最后
不知道你们用的什么环境,我一般都是用的Python3.6环境和pycharm解释器,没有软件,或者没有资料,没人解答问题,都可以免费领取(包括今天的代码),过几天我还会做个视频教程出来,有需要也可以领取~
给大家准备的学习资料包括但不限于:
Python 环境、pycharm编辑器/永久激活/翻译插件
python 零基础视频教程
Python 界面开发实战教程
Python 爬虫实战教程
Python 数据分析实战教程
python 游戏开发实战教程
Python 电子书100本
Python 学习路线规划
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-ZVf6uke0-1712711313114)]