python的import详解,史上最细,2024年最新15个经典面试问题及回答思路

#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.pya.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.pyfrom ..folder2.c import f3

|—— python

| |—— demo.py

| |—— folder1

| |—— a.py

| |—— b.py

| |—— folder2

| |—— c.py

| |—— d.py

运行 demo.pya.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.pyimport 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开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img



既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Python开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024c (备注Python)
img

最后

不知道你们用的什么环境,我一般都是用的Python3.6环境和pycharm解释器,没有软件,或者没有资料,没人解答问题,都可以免费领取(包括今天的代码),过几天我还会做个视频教程出来,有需要也可以领取~

给大家准备的学习资料包括但不限于:

Python 环境、pycharm编辑器/永久激活/翻译插件

python 零基础视频教程

Python 界面开发实战教程

Python 爬虫实战教程

Python 数据分析实战教程

python 游戏开发实战教程

Python 电子书100本

Python 学习路线规划

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
img

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)]

  • 11
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值