从同名脚本(或附近脚本)导入库会引发“AttributeError:模块没有属性”或 ImportError 或 NameError
- 2024-11-15 08:36:00
- admin 原创
- 21
问题描述:
我有一个名为的脚本requests.py
需要使用第三方requests
包。该脚本无法导入该包,或者无法访问其功能。
为什么它不起作用?我该如何修复它?
尝试简单导入然后使用该功能会导致AttributeError
:
import requests
res = requests.get('http://www.google.ca')
print(res)
Traceback (most recent call last):
File "/Users/me/dev/rough/requests.py", line 1, in <module>
import requests
File "/Users/me/dev/rough/requests.py", line 3, in <module>
requests.get('http://www.google.ca')
AttributeError: module 'requests' has no attribute 'get'
在较新版本的 Python 中,错误消息改为:AttributeError: partially initialized module 'requests' has no attribute 'get' (most likely due to a circular import)
。
使用特定名称的 from-import 会导致ImportError
:
from requests import get
res = get('http://www.google.ca')
print(res)
Traceback (most recent call last):
File "requests.py", line 1, in <module>
from requests import get
File "/Users/me/dev/rough/requests.py", line 1, in <module>
from requests import get
ImportError: cannot import name 'get'
在较新版本的 Python 中,错误消息改为:ImportError: cannot import name 'get' from partially initialized module 'requests' (most likely due to a circular import) (/Users/me/dev/rough/requests.py)
。
对包内的模块使用 from-import 会导致不同的结果ImportError
:
from requests.auth import AuthBase
Traceback (most recent call last):
File "requests.py", line 1, in <module>
from requests.auth import AuthBase
File "/Users/me/dev/rough/requests.py", line 1, in <module>
from requests.auth import AuthBase
ImportError: No module named 'requests.auth'; 'requests' is not a package
使用星号导入然后使用该功能会引发NameError
:
from requests import *
res = get('http://www.google.ca')
print(res)
Traceback (most recent call last):
File "requests.py", line 1, in <module>
from requests import *
File "/Users/me/dev/rough/requests.py", line 3, in <module>
res = get('http://www.google.ca')
NameError: name 'get' is not defined
如果您故意将模块命名为与现有模块相同的名字并想要处理这种情况,请参阅当我的项目有一个同名的模块时,如何从标准库导入?(如何控制 Python 在哪里查找模块?)
解决方案 1:
发生这种情况的原因是,名为的本地模块requests.py
会遮盖requests
您尝试使用的已安装模块。当前目录被添加到 前面sys.path
,因此本地名称优先于已安装的名称。
出现这种情况时,一个额外的调试技巧是仔细查看 Traceback,并意识到所讨论的脚本的名称与您尝试导入的模块相匹配:
注意您在脚本中使用的名称:
File "/Users/me/dev/rough/requests.py", line 1, in <module>
您尝试导入的模块:requests
将您的模块重命名为其他名称以避免名称冲突。
Python 可能会在您的文件requests.pyc
旁边生成一个文件(在 Python 3 中的目录中)。重命名后也请删除该文件,因为解释器仍将引用该文件,从而重新生成错误。但是,如果该文件已被删除,则该文件不会影响您的代码。requests.py
`__pycache__pyc
pycache
py`
在示例中,将文件重命名为my_requests.py
,删除requests.pyc
,然后再次运行即可成功打印<Response [200]>
。
注意:这种情况不仅发生在将文件命名为您尝试导入的模块时。如果您将文件命名为与您直接导入的模块导入的模块相同的名称,也会发生这种情况。例如,调用一个文件copy.py
并尝试import pandas
从那里执行,将导致
ImportError: cannot import name 'copy' from 'copy'
这是因为pandas
imports copy
。这里没有神奇的解决方案,因为您不可能知道世界上所有模块的名称,但是经验法则是尝试使模块名称尽可能唯一,并在出现此类错误时尝试更改名称。
解决方案 2:
发生此错误的原因是用户创建的脚本与库文件名发生名称冲突。但请注意,此问题可能是间接导致的。可能需要进行一些侦查工作才能找出导致问题的文件。
例如:假设您有一个mydecimal.py
包含 的脚本import decimal
,打算使用标准库decimal
库进行带有十进制数的精确浮点计算。这不会造成问题,因为没有标准库mydecimal
。但是,恰巧 导入了decimal
(numbers
另一个标准库模块)供内部使用,因此在您的项目中调用的脚本numbers.py
会导致问题。
.py
如果在跟踪自身并重命名或删除项目中的相应文件后仍然遇到此类问题,还请检查Python 在导入模块时用于缓存字节码编译的.pyc
文件。在 3.x 中,这些文件将存储在具有特殊名称的文件夹中;删除此类文件夹和文件是安全的,并且可以隐藏它们(但您通常不想这样做)。__pycache__
解决方案 3:
概括
当项目中的 Python 源文件与某些外部库模块(标准库或已安装的第三方包)同名时,就会出现此类问题。当尝试import
从外部库(需要使用绝对导入)时,会发现项目自己的模块,从而导致各种错误,具体取决于具体细节。
一般情况下,修复该问题最简单的方法是重命名受影响的文件。可能还需要找到并删除任何相应的文件.pyc
。(这是非常安全的;这些文件只是之前将 Python 源代码转换为 Python 虚拟机的字节码的工作的缓存.class
,类似于Java 中的文件。)
当首选项目自己的模块时,这是因为 Python 搜索模块源代码以进行绝对导入的方式。根据 Python 的具体启动方式,中定义的模块搜索路径sys.path
通常从当前项目内的路径开始。在查看任何标准库或第三方库文件夹之前,Python 将首先在那里查看。这个搜索过程不关心导入模块所在的文件夹;任何相对路径都是相对于进程的当前工作目录,而不是导入模块。(但是,标准库路径通常是绝对路径,并且靠近的末尾sys.path
。)
AttributeError
出现此AttributeError
错误是因为项目自己的模块没有定义调用代码想要从外部库模块使用的函数、类等。因此,'module' object
代表具有指定名称的项目模块的has no attribute
,所以这正是错误消息所声称的。
在极少数不幸的情况下,项目自己的模块可能会定义具有相同名称的内容,从而执行不同的操作。这可能会导致任何类型的异常或其他逻辑错误。例如,如果修改了问题中的第一个示例:
import requests
def get():
pass
res = requests.get('http://www.google.ca')
print(res)
现在TypeError
将会引发,因为代码将尝试get
使用错误数量的参数来调用本地定义的函数。
ImportError
使用特定的 from-import 会导致错误报告不同,因为现在在导入过程本身中检测到了问题。在问题示例中,from requests import get
意味着,不是创建一个requests
命名模块的新全局名称,而是应该有一个新的全局名称get
来命名该模块中的函数(在普通导入后将被引用的函数requests.get
)。这需要从该模块查找get
属性;但由于加载了错误的模块,该属性查找失败。较新版本的 Python 将此报告为可能的循环导入。
其他导入尝试可能会导致不同的ImportError
s 抱怨导入的模块“不是包”。这是不言自明的:在问题的示例中,requests
是一个普通模块(因为它是由源代码文件定义的requests.py
),但所需的requests
- 由第三方库定义的 - 是一个包(由requests
其他地方的文件夹中的几个文件定义,包括一个__init__.py
定义一些不是模块的顶级包内容的文件,例如get
函数)。
NameError
使用星型导入(如from requests import *
)不会直接失败 - 它会为所有模块内容创建全局名称。但是,由于问题示例中的模块不包含任何名为 的内容get
,因此其尝试星型导入本身也不会定义该名称。因此,NameError
会发生 —— 尝试使用不存在的全局名称的正常错误。
加重因素
Python 的默认
import
系统基于名称,而不是文件路径,并且 Python不区分“(驱动程序)脚本文件”和“库(模块)文件”。任何 Python 源代码都可以被import
编译。尽管 Python 缓存了模块导入,但主脚本通常不会在此缓存中,这意味着它完全有能力尝试import
自身。这就是问题示例中发生的情况:由于requests
尚未被import
编译,缓存中没有任何内容,因此在驱动程序脚本(名为requests.py
)中尝试import requests
将搜索要导入的内容,并找到驱动程序脚本(即其自己的源文件)。
这只会在导入时导致问题,如果模块尝试在其自己的初始化中使用该导入,例如通过执行 from-import。否则,问题将被推迟,导致(通常)AttributeError
或NameError
在尝试使用该功能时。另请参阅:
* 使用相互或循环导入时会发生什么?
* 为什么循环导入似乎在调用堆栈的上层起作用,但随后在下面引发 ImportError ?
每当加载模块时(即从其源导入,而不是使用缓存),它自己的顶级
import
语句就会运行,从而递归地导致更多导入。任何这些间接导入都可能找到本地代码。Python 标准库不是包,并且主要使用绝对导入来引用其其他组件。例如,正如 Dave Rove 的回答所指出的那样,尝试decimal
从名为 的源文件numbers.py
或在具有此类源文件的项目中导入标准库模块可能会失败。
有一种特别有害的情况token.py
,项目中(或以交互模式启动 Python 时,当前工作目录)有一个名为的文件会导致交互式帮助中断:
$ touch token.py
$ python
Python 3.8.10 (default, Nov 14 2022, 12:59:47)
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> help
Type help() for interactive help, or help(object) for help about object.
>>> help()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.8/_sitebuiltins.py", line 102, in __call__
import pydoc
File "/usr/lib/python3.8/pydoc.py", line 66, in <module>
import inspect
File "/usr/lib/python3.8/inspect.py", line 40, in <module>
import linecache
File "/usr/lib/python3.8/linecache.py", line 11, in <module>
import tokenize
File "/usr/lib/python3.8/tokenize.py", line 35, in <module>
from token import EXACT_TOKEN_TYPES
ImportError: cannot import name 'EXACT_TOKEN_TYPES' from 'token' (/current/working directory/token.py)
回溯告诉我们所有需要知道的信息:调用help
会触发标准库的延迟导入pydoc
,这会间接尝试导入标准库token
,但会发现我们的token.py
不包含适当的名称。在旧版本的 Python 中,情况甚至更糟:tokenize
会从进行星型导入token
,然后其顶级代码会尝试使用在那里定义的名称,导致NameError
- 并且堆栈跟踪不会提及文件名token.py
。
.pyc
即使相应的源文件被重命名或删除, Python 也可以从缓存的字节码文件 ( ) 导入。这是为了加快导入速度,但它增加了一个额外的步骤来保持代码目录“干净”。在重命名任何.py
文件以解决此类问题后,请确保也检查字节码缓存。
解决问题
当然,标准建议简单明了:只需重命名.py
错误导入的文件,并删除.pyc
这些模块的所有缓存字节码文件。默认情况下,在 Python 3.x 中,文件夹中.pyc
任何.py
文件的文件都将放入具有特殊名称的子文件夹中__pycache__
;在 2.x 中,它们只是出现在相应.py
文件旁边。
然而,虽然这种方法可以快速解决大多数遇到此问题的人的直接问题,但该建议的可扩展性并不好。有很多潜在的问题名称;虽然大多数项目对包含名为 eg 的源文件不感兴趣os.py
,但其他一些名称可能更可取,或者更难解决。因此,以下是其他一些有用的技巧:
控制sys.path
当然,由于问题是由于sys.path
指定绝对导入应首先查看当前项目而引起的,因此只需更改 即可避免该问题sys.path
。
文档中将有问题的路径描述为“潜在的不安全路径”。使用交互式 Python 提示符时,它将是一个空字符串(相当于 的相对路径'.'
) - 即 Python 进程的当前工作目录,反映了使用 等所做的任何更改os.chdir
。对于正常启动的驱动程序脚本(python driver.py
),它将是脚本所在的目录,作为绝对路径(不一定是当前工作目录,因为可以在命令行上指定路径,如python path/to/driver.py
)。对于使用命令行标志运行的模块-m
,它将是初始当前工作目录(不一定是模块所在的位置,但也是不会受到影响的绝对os.chdir
路径)。
为了避免出现此路径sys.path
,请执行以下操作之一:
在 Python 3.11 及更高版本中,设置
PYTHONSAFEPATH
环境变量,或使用Python 的-P
命令行选项。在 Python 3.4 及更高版本中,使用
-I
命令行选项以隔离模式启动。但是,这还有其他几个影响:它将忽略环境变量(如PYTHONPATH
和PYTHONHOME
),并且它还将跳过将用户特定的 site-packages 目录添加到路径(因此代码将无法访问使用 Pip 选项安装的第三方库--user
)。如果所有其他方法都失败了,请考虑手动操作
sys.path
。这很混乱且容易出错,但这sys.path
只是带有文件路径的普通字符串列表,修改其内容将影响将来的import
。(不要尝试替换列表对象;这不会起作用,因为 Python 不会通过名称查找它sys.path
,而是使用硬编码的内部引用。无法从 Python 中销毁或替换该对象;sys.path
只是一个初始化以引用它的名称。)
请记住,删除“不安全”路径将阻止有意的绝对导入在包内工作。这对于一些小项目来说很不方便,但也是学习正确的包组织的一个很好的理由。说到这个:
在包中使用相对导入
一个组织良好的 Python 项目通常由一个或多个(通常只有一个)包组成,存储在主项目文件夹的子文件夹中,以及一个或多个放置在包子文件夹之外的驱动程序脚本。驱动程序脚本通常使用单个绝对导入来访问包功能,同时实现一些简单的包装器逻辑(例如解析命令行参数、格式化和报告未捕获的异常等)。同时,包将始终使用相对导入来获取其自身内容,并仅在需要访问其他包(标准库和第三方依赖项)时使用绝对导入。
例如,一个项目可能按如下方式组织:
project
├── src
│ └── my_package
│ └── x.py
│ └── y.py
│ └── z.py
└── driver.py
代码driver.py
将使用绝对导入到包中,例如:
from my_package.x import entry_point
if __name__ == '__main__':
entry_point()
(如果需要逻辑来解析命令行参数,它通常应该放在驱动程序中而不是包的代码中。)
然后,包内的代码将使用相对导入- 因此x.py
可能包含类似以下内容:
from .y import first_thing
from .z import second_thing
def entry_point():
first_thing()
second_thing()
这样可以兼顾两方面:初始绝对导入设置顶级包,以便相对导入可以工作,而相对导入将避免依赖于配置sys.path
。即使不采取配置步骤sys.path
,它通常也会包含包含驱动程序脚本的文件夹,但不包含任何包文件夹;因此,这也会自动避免导入路径冲突。(除非指定相应的包路径,否则绝对导入将找不到包内容;但通常当它找到时,从当前包导入是故意的。)
这也避免了为下一个依赖此项目的项目设置陷阱。例如,假设我们实现并发布了一个API
包,而其他人编写了依赖此项目的Client
包。由于代码将使用相对导入来实现其他功能(例如),因此项目本身不会造成问题。API
`APIAPI
from . import functionalityClient
functionality.py`
当然,为了使初始绝对导入正常工作,需要在 中提及顶级包文件夹。但是,这通常通过安装包sys.path
来完成,因此不会造成问题。
禁用字节码缓存
如果需要重命名文件,.pyc
如果文件根本不存在,那么避免文件出现问题会更容易。毕竟,它们不是必需的;它们只是为了加快程序后续运行的导入速度。
要抑制.pyc
文件生成,请使用-B
命令行选项或设置PYTHONDONTWRITEBYTECODE
环境变量。(没有内置解决方案可以删除所有现有.pyc
文件,但使用标准库模块等手动实现这一点很容易shutil
。)
避免使用有问题的名称
考虑使用第三方工具(例如 IDE 插件)来警告项目中标准库或第三方库使用的文件名。在 Python 3.10 及更高版本中,标准库模块名称的完整列表也可用作sys.stdlib_module_names
。这包括当前 Python 安装中可能不存在的名称(例如特定于操作系统的组件或有时被禁用或省略的内容,例如 Tkinter)。
解决方案 4:
只需将导致错误的导入放到最后即可
ex:
import requests
import flask
import numpy
如果import flask
导致错误则将其移至导入的底部
solution:-
import requests
import numpy
import flask
解决方案 5:
循环依赖:当两个或多个模块相互依赖时就会发生这种情况。这是因为每个模块都是根据另一个模块定义的。
如果您收到类似于以下请求模块错误的循环导入错误。
AttributeError: partially initialized module 'requests' has no attribute 'post' (most likely due to a circular import)
Please try to rename the file.
这个错误的错误通常是由于与您尝试导入requests
模块的文件名冲突造成的。
我也遇到了同样的问题,我的文件名是email.py
,我试图导入请求模块。因此,它与发生了一些冲突email.parser
。因此,我将文件名从 更改为email.py
,email1.py
然后它就成功了。
有关循环依赖的更多信息:https://stackabuse.com/python-circular-imports/
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件