Python学习:import的正确用法

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

将要建立文件的结构为:

Tree
|____ m1.py
|____ m2.py
|____ Branch
     |____m3.py
     |____m4.py

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

import os
import m2
m2.printSelf()

在m2.py写入代码:

def printSelf():
	print('In m2')

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

  • import module_name。即import后直接接模块名。在这种情况下,Python会在两个地方寻找这个模块,第一是sys.path(通过运行代码import sys; print(sys.path)查看),os这个模块所在的目录就在列表sys.path中,一般安装的Python库的目录都可以在sys.path中找到(前提是要将Python的安装目录添加到电脑的环境变量),所以对于安装好的库,我们直接import即可。第二个地方就是运行文件(这里是m1.py)所在的目录,因为m2.py和运行文件在同一目录下,所以上述写法没有问题。

用上述方法导入原有的sys.path中的库没有问题。但是,最好不要用上述方法导入同目录下的文件!因为这可能会出错。演示这个错误需要用到import语句的第二种写法,所以先来学一学import的第二种写法。在Tree目录下新建一个目录Branch,在Branch中新建文件m3.py,m3.py的内容如下:

def printSelf():
	print('In m3')

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

from Branch import m3
m3.printSelf()

总结import语句的第二种用法:

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

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

def printSelf():
	print('In m4')

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

import m4
def printSelf():
	print('In m3')

这时候运行m1.py就会报错了,说没法导入m4模块。为什么呢?我们来看一下导入流程:m1使用from Branch import m3导入m3,然后在m3.py中用import m4导入m4。看出问题了吗?m4.py和m1.py不在同一目录,怎么能直接使用import m4导入m4呢。(可以试试直接在Tree目录下新建另一个m4.py文件,你会发现再运行m1.py就不会出错了,只不过导入的是第二个m4.py了)

面对上面的错误,使用python2运行m1.py就不会报错,因为在python2中,上面提到的import的两种写法都属于相对导入,而在python3中,却属于绝对导入。话说到了这里,就要牵扯到import中最关键的部分了——相对导入和绝对导入。

我们还是谈论python3的import用法。上面提到的两种写法属于绝对导入,即用于导入sys.path中的包和运行文件所在目录下的包。对于sys.path中的包,这种写法毫无问题;导入自己写的文件,如果是非运行入口文件(上面的m1.py是运行入口文件,可以使用绝对导入),则需要相对导入。

比如对于非运行入口文件m3.py,其导入m4.py需要使用相对导入:

from . import m4
def printSelf():
	print('In m3')

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

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

不知道你有没有留神上面的一句话——“上面的m1.py是运行入口文件,可以使用绝对导入”,这句话是没问题的,也和我平时的做法一致。那么,运行入口文件可不可以使用相对导入呢?比如m1.py内容改成:

from .Branch import m3
m3.printSelf()

答案是可以,但不能用python m1.py命令,而是需要进入到Tree所在的目录,使用python -m Tree.m1来运行。为什么?关于前者,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__':
	main()

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

Traceback (most recent call last): 
File "m1.py", line 1, in from .Branch import m3 ModuleNotFoundError: 
No module named '_main_.Branch'; 
'__main__' is not a package

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

那为什么python -m Tree.m1就可以呢?

执行指令中的-m是为了让Python预先import你要的package或module给你,然后再执行script。

即不把m1.py当作运行入口文件,而是也把它当作被导入的模块,这就和非运行入口文件有一样的表现了。

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

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

(之前看到了一个大型项目,其运行入口文件有一大堆的相对导入,我还傻乎乎地用python直接运行它。之后看到他给的样例运行命令是带了-m参数的。现在才恍然大悟。)

理解import的难点差不多就这样了。

下面说一说import的其他简单但实用的用法。

  • 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。直面问题的第一步是去了解熟悉它,最好是能复现它,将错误暴露在阳光之下。仍然是上面四个文件,稍作修改,四个文件如下:

# m1.py
from Branch import m3
m3.printSelf()
# m2.py
def printSelf():
	print('module2')
# m3.py
from .. import m2 # 复现的关键在这 #
print(__name__)
def printSelf():
	print('In m3')
# m4.py
def printSelf():
	print('In m4')

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

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Python学习:import的正确用法 - Python技术站

(0)
上一篇 2023年4月2日 下午5:04
下一篇 2023年4月2日

相关文章

  • Python递归的几个经典案例

    当我们碰到诸如需要求阶乘或斐波那契数列的问题时,使用普通的循环往往比较麻烦,但如果我们使用递归时,会简单许多,起到事半功倍的效果。这篇文章主要和大家分享一些和递归有关的经典案例,结合一些资料谈一下个人的理解,也借此加深自己对递归的理解和掌握一些递归基础的用法。 一、递归的简介 1、递归的百度百科定义 程序调用自身的编程技巧称为递归( recursion)。 …

    Python开发 2023年4月2日
    00
  • Python教程:selenium模块用法教程

    1.介绍 selenium最初是一个自动化测试工具,而爬虫中使用它主要是为了解决requests无法直接执行JavaScript代码的问题 selenium本质是通过驱动浏览器,完全模拟浏览器的操作,比如跳转、输入、点击、下拉等,来拿到网页渲染之后的结果,可支持多种浏览器 from selenium import webdriver browser=webd…

    Python开发 2023年3月31日
    00
  • 获取Python函数信息的方法

    Python的反射机制可以动态获取对象信息以及动态调用对象,本文介绍如何获取对象中的函数注释信息以及参数信息。 定义一个Person类: class Person(): def talk(self, name, age, height=None): “””talk function :return: “”” print(f”My name is {name}…

    python 2023年4月18日
    00
  • Python中高阶函数与装饰器教程

    1高阶函数 1.1 数学概念 回顾下数学知识: y=f(x) 这是最开始接触的普通函数 y=g(f(x)) 这个就是我们接触到的高阶函数 在数学和计算机科学中,高阶函数至少应当是满足下面一个条件的函数: 1)接受一个或者多个函数作为参数 2)输出一个函数 程序中我们的高阶函数也类似 示例计数器的函数: def counter(base): def inc(s…

    Python开发 2023年3月31日
    00
  • Python学习:定义函数的默认参数和可变参数

    一、默认参数 定义函数的时候,还可以有默认参数。 例如Python自带的 int() 函数,其实就有两个参数,我们既可以传一个参数,又可以传两个参数: >>> int(‘123’) 123 >>> int(‘123’, 8) 83 int()函数的第二个参数是转换进制,如果不传,默认是十进制 (base=10),如果传了,…

    Python开发 2023年4月2日
    00
  • Python模块:subprocess模块教程

    一.subprocess模块 subprocess是Python 2.4中新增的一个模块,它允许你生成新的进程,连接到它们的 input/output/error 管道,并获取它们的返回(状态)码。这个模块的目的在于替换几个旧的模块和方法,如: os.system os.spawn* 1.subprocess模块中的常用函数 函数 描述 subprocess…

    Python开发 2023年4月2日
    00
  • Python关于异常处理的教程

    一、什么是异常 异常就是程序运行时发生错误的信号(在程序出现错误时,则会产生一个异常,若程序没有处理它,则会抛出该异常,程序的运行也随之终止),在python中,错误触发的异常如下 1 语法错误 语法错误,根本过不了python解释器的语法检测,必须在程序执行前就改正。 # 语法错误示范一 if # 语法错误示范二 def test: pass # 语法错误…

    Python开发 2023年3月31日
    00
  • Python迭代器是啥?

    迭代器:迭代的工具。迭代是更新换代,如你爷爷生了你爹,你爹生了你,迭代也可以说成是重复,并且但每一次的重复都是基于上一次的结果来的。如计算机中的迭代开发,就是基于软件的上一个版本更新。以下代码就不是迭代,它只是单纯的重复 while True: print(‘*’*10) 一、可迭代对象 python中一切皆对象,如 x = 1 name = ‘nick’ …

    Python开发 2023年3月31日
    00
合作推广
合作推广
分享本页
返回顶部