python学习之路之:import(详细介绍import的各种调用原理和使用方法)

import 使用

  • 【介绍】
  • 【用法】
    • import语句的第一种用法::
    • **import module_name。**
    • import语句的第二种用法:
    • if __name__ == '__main__':
  • 【其他用法】
    • as 改module_name名
    • 只使用模块中的某些内容
    • 导入元素过多

【介绍】

import语句用来导入其他python文件(称为模块module),使用该模块里定义的类、方法或者变量,从而达到代码复用的目的。

【用法】

首先,先建立一个文件夹testfile作为工作目录,并在其内建立两个文件test1.py和test2.py,
在test1.py写入代码:

import os
import test2

test2.printSelf()

在test2.py写入代码:

def printtittle():
	print('I'm test2')

打开命令行,进入到testfile目录下,敲下python test1.py运行,发现没有报错,且打印出I’m test2,说明这样使用import没有问题。
由此我们总结出import语句的第一种用法。

import语句的第一种用法::

import module_name。

即import后直接接模块名。在这种情况下,Python会在两个地方寻找这个模块,
第一是:sys.path
(通过运行代码import sys; print(sys.path)查看),os这个模块所在的目录就在列表sys.path中,一般安装的Python库的目录都可以在sys.path中找到(前提是要将Python的安装目录添加到电脑的环境变量),所以对于安装好的库,我们直接import即可。当你import的时候,python解释器只会在sys.path这个变量(一个list,你可以print出来看)里面的路径中找可能匹配的package或module。
而一个package跟一个普通文件夹的区别在于:
package的文件夹中多了一个__init__.py文件。换句话说,如果你在某个文件夹中添加了一个__init__.py文件,则python就认为这个文件夹是一个python中的package。

init.py文件的内容可以是空的(package里面必备这个模块,.py的文件就是模块,这个知识点要知道),它只是告诉python当前文件夹是一个python中的package。当然,你可以在这个__init__.py的module里面添加一些代码,这些代码会在import这个package的时候运行,也就是package下__init__.py模块,会在import package后,立刻会从无缩进的地方开始执行代码。

所以,请确保你要import的py文件所在的目录有__init__.py文件。(参考)
python学习之路之:import(详细介绍import的各种调用原理和使用方法)_第1张图片
第二是:运行文件
(这里是test1.py)所在的目录,因为test2.py和运行文件test1.py在同一目录下,所以上述写法没有问题。
用上述方法导入原有的sys.path中的库没有问题。但是,最好不要用上述方法导入同目录下的文件!因为这可能会出错。演示这个错误需要用到import语句的第二种写法,所以先来学一学import的第二种写法。
用法二:testtest
在testfile目录下新建一个目录Branch,在Branch中新建文件test3.py,test3.py的内容如下:

def printSelf():
	print('I'm test3')

如何在test1中导入test3.py呢,请看更改后的test1.py:

from Branch import test3
test3.printSelf()

import语句的第二种用法:

  • from package_name import module_name。
    一般把模块组成的集合称为包(package)。与第一种写法类似,Python会在sys.path运行文件目录这两个地方寻找包,然后导入包中名为module_name的模块。

现在我们来说明为什么不要用import的第一种写法来导入同目录下的文件。在Branch目录下新建test4.py文件,test4.py的内容如下:

def printSelf():
	print('I'm test4')

然后我们在test3.py中直接导入test4,test3.py变为:

import test4
def printSelf():
	print('I'm test3')

这时候运行test1.py就会报错了,说没法导入test4模块。why?
我们来看一下导入流程:test1使用from Branch import test3导入test3,然后在test3.py中用import test4导入test4
看出问题了吗?
test4.py和test1.py不在同一目录,所以在test1运行时,因为不能直接使用import m4导入m4所以报错。如果我们直接在testfile目录下新建另一个test4.py文件,你会发现再运行test1.py就不会出错了,只不过导入的是与test1同一目录下的test4.py而不是与test3同目录的test4

面对上面的错误,使用python2运行test1.py就不会报错,因为在python2中,上面提到的import的两种写法都属于相对导入,而在python3中,却属于绝对导入。

话说到了这里,就要牵扯到import中最关键的部分了——相对导入和绝对导入

上面提到的两种写法属于绝对导入,即用于导入sys.path中的包和运行文件所在目录下的包。对于sys.path中的包,这种写法毫无问题;导入自己写的文件,如果是非运行入口文件(可以理解为test2/3/4.py文件)(上面的test1.py是运行入口文件,可以使用绝对导入),则需要相对导入。
testtest
比如对于非运行入口文件test3.py,其导入test4.py需要使用相对导入:

修改如下

from . import test4
def printtestSelf():
	print('I'm test3')

这时候再运行test1.py就ok了。列举一下相对导入的写法:

  • from . import module_name
    导入和自己同目录下的模块。
  • from .package_name import module_name
    导入和自己同目录的包的模块。(同级导入)
  • from … import module_name
    导入上级目录的模块。(上级导入)
  • from …package_name import module_name
    导入位于上级目录下的包的模块。
    当然还可以有更多的,每多一个点就多往上一层目录。
    每多一个点就多往上一层目录。
    每多一个点就多往上一层目录。

上面的test1.py是运行入口文件,可以使用绝对导入,这句话是没问题的,和平时的做法一致。那么,运行入口文件可不可以使用相对导入呢?比如test1.py内容改成:

from .Branch import test3
test3.printSelf()

答案是可以,但不能用python test1.py命令,而是需要进入到testfile所在的目录,使用python -m testfile.test1来运行。

为什么?关于前者,PEP 328提案中的一段文字好像给出了原因:

Relative imports use a module's _name_ attribute to determine that module's position in the package hierarchy. If the module's name does not contain any package information (e.g. it is set to '__main__') then relative imports are resolved as if the module were a top level module, regardless of where the module is actually located on the file system.

为了加深学习,我们引入另外一个知识点

if name == ‘main’:

下面一段代码:

if __name__ == '__main__':
	main()

意思是:

  • 如果运行了当前文件,则__name__变量会置为__main__,然后会执行main函数,
  • 如果当前文件是被其他文件作为模块导入的话,则__name__为模块名,不等于__main__,就不会执行main函数。
  • 比如对于上述更改后的test1.py,执行python test1.py命令后,会报如下错误:

Traceback (most recent call last): File “test1.py”, line 1, in from .Branch import test3 ModuleNotFoundError: No module named ‘main.Branch’; ‘main’ is not a package

据此我猜测执行python test1.py命令后,当前目录所代表的包’.'变成了__main__。

那为什么python -m testfile.test1就可以呢?那位台湾老师给出了解释:

执行指令中的-test是为了让Python预先import你要的package或module给你,然后再执行script。
即不把test1.py当作运行入口文件,而是也把它当作被导入的模块,这就和非运行入口文件有一样的表现了。

注意,在testfile目录下运行python -m test1是不可以的,会报 ImportError: attempted relative import with no known parent package的错误。因为test1.py中的from .Branch import test3中的. ,解释器并不知道是哪一个package。使用python -m testfile.test1,解释器就知道.对应的是testfile这个package。

那反过来,如果test1.py使用绝对导入(from Branch import test3),能使用python -m test1运行吗?我试了一下,如果当前目录是testfile就可以。如果在其他目录下运行,比如在testfile所在的目录(使用python -m testfile.test1运行),就不可以。这可能还是与绝对导入相关。

(许多大型项目,其运行入口文件有很多的相对导入,运行命令都是带了-m参数的。)

【其他用法】

理解import的难点差不多就这样了。下面说一说import的其他简单但实用的用法。

as 改module_name名

import moudle_name as alias
有些module_name比较长,之后写它时较为麻烦,或者module_name会出现名字冲突,可以用as来给它改名,如import numpy as np。

只使用模块中的某些内容

from module_name import function_name, variable_name, class_name。
上面导入的都是整个模块,有时候我们只想使用模块中的某些函数、某些变量、某些类,用这种写法就可以了。使用逗号可以导入模块中的多个元素。

导入元素过多

有时候导入的元素很多,可以使用反斜杠来换行,官方推荐使用括号。
from Tkinter import Tk, Frame, Button, Entry, Canvas, Text,
LEFT, DISABLED, NORMAL, RIDGE, END # 反斜杠换行
from Tkinter import (Tk, Frame, Button, Entry, Canvas, Text,
LEFT, DISABLED, NORMAL, RIDGE, END) # 括号换行(推荐)
说到这感觉import的核心已经说完了。再跟着上面的博客说一说使用import可能碰到的问题吧。

问题1描述:ValueError: attempted relative import beyond top-level package。直面问题的第一步是去了解熟悉它,最好是能复现它,让它躺在两跨之间任我们去践踏蹂躏。仍然是上面四个文件,稍作修改,四个文件如下:

# test1.py
from Branch import test3
test3.printSelf()
# test2.py
def printSelf():
	print('I'm test2')
# test3.py
from .. import test2 # 复现的关键在这 #
print(__name__)
def printSelf():
	print('I'm test3')
# test4.py
def printSelf():
	print('I'm test4')

运行python m1.py,就会出现该问题。问题何在?我猜测,运行m1.py后,m1代表的模块就是顶层模块(参见上面PEP 328的引用),而m3.py中尝试导入的m2模块所在的包(即testfile目录代表的包)比m1的层级更高,所以会报出这样的错误。怎么解决呢?将m1.py的所有导入改为相对导入,然后进入m1.py的上层目录,运行python -m testfile.m1即可。

你可能感兴趣的:(python)