当前位置: 首页 > 科技观察

关于Python导入模块,你可能没学透

时间:2023-03-15 19:29:20 科技观察

作为新手Python程序员,您需要学习的第一件事就是如何导入模块或包。但是我注意到,这些年来偶尔使用过Python的人,并不是都知道Python的导入机制其实是非常灵活的。在本文中,我们将探讨以下主题:常规导入使用from语句相对导入相对导入可选导入本地导入导入注意事项常规导入常规导入应该是最常见的使用的导入方式大概是这样的:importsys,你只需要使用import一词,然后指定要导入的模块或包。以这种方式导入的好处是可以一次导入多个包或模块:importos,sys,time这样虽然节省了空间,但违反了Python风格指南。Python风格指南建议将每个导入语句放在单独的行中。有时在导入模块时,您想重命名模块。这个特性很容易实现:importsysassystemprint(system.platform)上面的代码将我们导入的sys模块重命名为system。我们可以像以前一样调用模块的方法,但使用新的模块名称。还有一些子模块必须使用点符号导入。importurllib.error并不常见,但了解它也无妨。使用from语句导入很多时候您只想导入模块或库的一部分。让我们看看这在Python中是如何完成的:fromfunctoolsimportlru_cache上面这行代码允许您直接调用lru_cache。如果你以通常的方式导入functools,那么你必须像这样调用lru_cache:functools.lru_cache(*args)根据你的实际使用场景,上面的方法可能会更好。在复杂的代码库中,能够看到函数是从哪里导入的是很有用的。但是,如果你的代码维护得很好,模块化程度很高,那么只导入某个模块的部分内容是非常方便和简洁的。当然,你也可以使用from方法导入模块的全部内容,像这样:fromosimport*这种方法在少数情况下相当方便,但它也会弄乱你的命名空间。问题是您可能会定义一个与导入模块同名的变量或函数。这时,如果你试图在os模块中使用同名的变量或函数,你实际上会使用你自己定义的。因此,您可能会遇到一个相当混乱的逻辑错误。我建议全面导入的标准库中唯一的模块是Tkinter。如果您恰好在编写自己的模块或包,有些人建议您将__init__.py文件中的所有内容导入,以使模块或包更方便使用。我个人更喜欢显式导入而不是隐式导入。您也可以采取折衷方案,从一个包中导入多个项目:fromosimportpath、walk、unlinkfromosimportuname、remove在上面的代码中,我们从os模块中导入了5个函数。您可能已经注意到,我们通过多次从同一个模块导入来做到这一点。当然,如果你愿意,你也可以使用括号一次导入多个项目:fromosimport(path,walk,unlink,uname,remove,rename)这是一个有用的技巧,但你也可以用另一种方式来做:上面的反斜杠fromosimportpath,walk,unlink,uname,\remove,rename在Python中是一个续行符,告诉解释器这行代码继续到下一行。相对导入PEP328描述了引入相对导入的原因,以及选择了哪种语法。具体来说,就是用句点来判断如何相对导入其他包或模块。这样做的原因是为了避免在从标准库导入模块时发生意外冲突。这里我们以PEP328中给出的文件夹结构为例,看看相对导入是如何工作的:my_package/__init__.pysubpackage1/__init__.pymodule_x.pymodule_y.pysubpackage2/__init__.pymodule_z.pymodule_a.py在本地磁盘位置找到创建上面的内容文件和文件夹。在顶层__init__.py文件中,输入以下代码:from.importsubpackage1from.importsubpackage2接下来进入subpackage1文件夹,编辑其中的__init__.py文件,输入以下代码:from.importmodule_xfrom.importmodule_y现在编辑module_x.py文件,输入以下代码:from.module_yimportspamashamdefmain():ham()最后编辑module_y.py文件,输入以下代码:defspam():print('spam'*3)打开终端,cd到my_package包所在的文件夹,但不要进入my_package。在此文件夹下运行Python解释器。我使用IPython是因为它方便的自动完成功能:In[1]:importmy_packageIn[2]:my_package.subpackage1.module_xOut[2]:In[3]:my_package.subpackage1.module_x.main()spamspamspam相对导入用于您最终放入包中的代码。如果你写了很多高度依赖的代码,你应该使用这种导入方式。您会发现PyPI上许多流行的包也使用相对导入。另请注意,如果要跨多个文件级别导入,只需使用多个句点即可。但是,PEP328建议不超过两个级别的相对导入。另请注意,如果您将if__name__=='__main__'添加到module_x.py文件中,然后尝试运行该文件,您将收到无法理解的错误。编辑文件并尝试一下!from.module_yimportspamashamdefmain():ham()if__name__=='__main__':#Thiswon'twork!main()现在从终端转到subpackage1文件夹并执行以下命令:pythonmodule_x.py如果你使用如果你使用Python2、你应该看到下面的错误信息:Traceback(mostrecentcalllast):File"module_x.py",line1,infrom.module_yimportspamashamValueError:Attemptedrelativeimportinnon-package如果你使用的是Python3,错误信息大概是这样的:traceback(mostrecentcalllast):File"module_x.py",line1,infrom.module_yimportspamashamSystemError:Parentmodule''notloaded,cannotperformrelativeimport这意味着module_x.py是某个包中的模块,而你正试图在脚本中执行模式,但这种模式不支持相对导入。如果你想在你自己的代码中使用这个模块,你必须将它添加到Python的导入搜索路径中。最简单的方法如下:importsyssys.path.append('/path/to/folder/containing/my_package')importmy_package注意需要添加的是my_package上一个文件夹的路径,而不是my_package本身。原因是my_package是我们要使用的包,所以如果加上它的路径,那么这个包就得不到了。接下来我们来谈谈可选导入。可选导入(Optionalimports)如果你想先使用某个模块或包,但又想有一个没有这个模块或包的替代方案,你可以使用可选导入。这样做可以导入对一个软件的多个版本的支持或实现性能改进。以github2包中的代码为例:()]lxml包还使用了可选的导入方式:try:fromurlparseimporturljoinfromurllib2importurlopenexceptImportError:#Python3fromurllib.parseimporturljoinfromurllib.requestimporturlopen如上例所示,可选导入的使用非常普遍,是一项值得掌握的技巧。当你在本地使用部分导入时当你在范围内导入模块时,你是在执行本地导入。如果你在Python脚本文件的顶部导入模块,你是在将该模块导入到全局范围内,这意味着之后的任何函数或方法都可以访问模块。例如:importsys#globalscopedefsquare_root(a):#Thisimportisintothesquare_rootfunctionslocalscopeimportmathreturnmath.sqrt(a)defmy_pow(base_num,power):returnmath.pow(base_num,power)if__name__=='__main__':print(square_root(49))print(my_pow(2,3))在这里,我们导入sys模块进入全局范围,但我们不使用这个模块。然后,在square_root函数中,我们将math模块导入到函数的局部范围内,这意味着math模块只能在square_root函数内部使用。如果我们尝试在my_pow函数中使用数学,则会引发NameError。尝试执行此脚本,看看会发生什么。使用本地范围的好处之一是您使用的模块可能需要很长时间才能导入。如果是这种情况,将它放在一个不常调用的函数中可能更有意义,而不是直接放在全局范围内。导入。老实说,我几乎从不使用本地导入,主要是因为如果模块中到处都有导入语句,很难说清楚为什么以及该怎么做。按照惯例,所有导入语句都应位于模块的顶部。导入注意事项程序员在导入模块时常犯几个错误。这里我们介绍两个。循环导入(Shadowedimports,暂译为coverageimports)先来看看循环导入。循环导入如果您创建两个相互导入的模块,就会发生循环导入。例如:#a.pyimportbdefa_test():print("ina_test")b.b_test()a_test()然后在同一文件夹中创建另一个模块并将其命名为b.py。importadefb_test():print('Intest_b"')a.a_test()b_test()如果你运行任何一个模块都会引发AttributeError。这是因为两个模块都试图相互导入。简而言之,模块a想要导入模块b,但由于模块b也在尝试导入模块a(此时正在执行),模块a将无法完成模块b的导入。我见过一些hack来解决这个问题,但一般来说,what你应该做的是重构你的代码,这样就不会发生这种情况。当你创建一个与标准库中的模块同名的模块时,就会发生覆盖导入,如果你导入这个模块,就会发生覆盖导入。例如,创建一个名为math.py的文件并在其中写入以下代码:importmathdefsquare_root(number):returnmath.sqrt(number)square_root(72)现在打开终端并尝试运行该文件,您将得到以下Traceback信息(追溯):追溯(mostrecentcallast):文件“math.py”,line1,inimportmathFile“/Users/michael/Desktop/math.py”,line6,insquare_root(72)File“/Users/michael/Desktop/math.py",line4,insquare_rootreturnmath.sqrt(number)AttributeError:module'math'hasnoattribute'sqrt'这是怎么回事?实际上,当你运行这个文件时,Python解释器首先在脚本当前运行的文件夹中寻找一个名为math的模块。在这个例子中,解释器找到了我们正在执行的模块并尝试导入它。但是我们的模块中没有名为sqrt的函数或属性,所以抛出AttributeError。总结在本文中,我们已经谈了很多关于导入的内容,但还有一些内容我们没有涉及。PEP302中引入了导入挂钩,它允许一些非常酷的功能,比如直接从github导入。Python标准库中还有一个importlib模块,值得一试。当然你也可以看看别人写的代码,继续挖掘更多有用的妙招。