为什么 tkinter 模块在通过命令行运行时会引发属性错误,但在通过 IDLE 运行时不会?
问题描述
与通过 IDLE 的 run module f5
命令运行相比,通过命令行运行代码会引发错误是否有原因?
Is there a reason why the code will raise an error when run via the command line compared to when run via IDLE's run module f5
command?
最近我一直在努力提高我的代码的可读性和健壮性.结果,我一直在尝试删除所有 from module import *
行.我曾经使用 from tkinter import *
并且我的这行代码运行良好:
Recently I've been trying to improve the readability and robust-ness of my code. As a result I've been trying to remove all the from module import *
lines. I used to use from tkinter import *
and this line of my code worked perfectly fine:
self.path = filedialog.askdirectory()
但现在我已将 from tkinter import *
更改为 import tkinter as tk
并相应地更改了代码:
But now I have changed from tkinter import *
to import tkinter as tk
and I have changed the code accordingly:
self.path = tk.filedialog.askdirectory()
名为 GUI.py 的文件使用以下命令导入此文件:from lib.filesearch import *
(我提到的代码行位于文件搜索文件中.)
A file called GUI.py imports this file with: from lib.filesearch import *
(the line of code I mentioned resides within the filesearch file.)
我通过 IDLE 运行我的代码,一切都很好.我的 GUI 仍然有效,并且行 self.path = tk.filedialog.askdirectory()
正常工作,但是,当我通过 windows 命令行运行代码时,我收到错误:
I run my code via IDLE and everything is fine. My GUI still works and the line self.path = tk.filedialog.askdirectory()
works like normal however, when I run the code through windows command line I get the error:
AttributeError: 'module' object has no attribute 'filedialog'
以下是我的代码中的相关位:
Here are the relevant bits from my code:
来自文件搜索.py
import tkinter as tk
def get_path(self):
"""Store user chosen path to search"""
self.paths = tk.filedialog.askdirectory(initialdir = FileSearch.DEFAULT)
return self.paths
来自 GUI.py
from lib.filesearch import *
def Browse(self):
self.BrowseB['state']='disabled'
self.p=self.CrawlObj.get_path()
self.AddText('Searching from Path: ' + str(self.p))
self.BrowseB['state']='normal'
与此不同 问题 我只安装了一个版本的 python.即 Python34.
Unlike this question I only have one version of python installed. Namely, Python34.
解决方案
首先我想说:如果你知道你会使用子模块,总是显式地导入它们.这个答案的结尾有一个更令人信服的案例,这很重要.
I want to start by saying: always explicitly import submodules if you know you will use them. The end of this answer has a more compelling case where this is important.
由于 tkinter
的结构,您必须显式导入子模块才能加载:
Because of the structure of tkinter
you must explicitly import submodules for them to load:
import tkinter as tk
print(hasattr(tk,"filedialog")) # in a standard interpreter will print false
import tkinter.filedialog
print(hasattr(tk,"filedialog")) # should always print true after explicit import
您不需要在 IDLE 中执行此操作的原因是,在您的代码运行之前,IDLE 在后台设置了一些东西并最终导入了一些 tkinter 库.维护者之一 已评论这实际上是 IDLE 中的一个错误.
the reason you don't need to do this in IDLE is that before your code is run IDLE sets up some stuff in the background and ends up importing some of the tkinter libraries. One of the maintainers has commented that this is effectively a bug in IDLE.
在 python 3.6.5 中(可能更早,仅检查此版本)此特定差异已得到修复,因此除了我在下面显示的 2 个模块外,其他所有模块都不会出现此问题.em>
In python 3.6.5 (and possibly earlier, only checked this version) this specific discrepancy has been fixed so it no longer happens for all but 2 modules I show below.
在任何版本中,您都可以看到加载了如下代码的子模块列表:
in any version you can see a list of submodules that are loaded with some code like this:
Python 3.6.5 (v3.6.5:f59c0932b4, Mar 28 2018, 03:03:55)
# standard interpreter
>>> import sys
>>> len(sys.modules) #total number of modules automatically loaded
71
>>> sorted(name for name in sys.modules.keys() if ("." in name)) #submodules loaded
['collections.abc', 'encodings.aliases', 'encodings.latin_1', 'encodings.utf_8', 'importlib._bootstrap', 'importlib._bootstrap_external', 'importlib.abc', 'importlib.machinery', 'importlib.util', 'os.path']
>>> len(_) #number of submodules
10
在 IDLE 中:
Python 3.6.5 (v3.6.5:f59c0932b4, Mar 28 2018, 03:03:55)
# IDLE
>>> import sys
>>> len(sys.modules)
152
>>> sorted(name for name in sys.modules.keys() if ("." in name and "idlelib" not in name))
['collections.abc', 'encodings.aliases', 'encodings.ascii', 'encodings.latin_1', 'encodings.utf_8', 'importlib._bootstrap', 'importlib._bootstrap_external', 'importlib.abc', 'importlib.machinery', 'importlib.util', 'os.path', 'tkinter.constants', 'urllib.parse']
>>> len(_) #number of submodules not directly related to idlelib.
13
tkinter.constants
是在您刚刚 import tkinter
时加载的,所以在我测试的版本中,这个问题仍然存在于仅 urllib.parse
和 encodings.ascii
(和 idlelib
模块,但通常生产代码不使用它)
tkinter.constants
is loaded when you just import tkinter
so as of the version I tested, this issue still exists for only urllib.parse
and encodings.ascii
(and idlelib
modules but generally production code doesn't use that)
这不一定是 IDLE 特定的问题,更糟糕的问题是子模块是否由您使用的另一个库加载.以如下代码为例:
This isn't necessarily an IDLE specific issue though, a worse issue is if the submodule is loaded by another library you use. Take the following code as an example:
>>> import pandas
>>> import http
>>> http.client
<module 'http.client' from '.../http/client.py'>
现在假设我们编写了一些仍然使用 http.client
但没有使用 pandas 的其他代码:
now lets say we wrote some other code that still used http.client
but didn't use pandas:
>>> import http
>>> http.client
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: module 'http' has no attribute 'client'
这样,当使用它的代码加载 http.client
时,您可能会得到一个子模块,该子模块可以正常工作,可能是通过使用碰巧使用它但会失败的库.
This way you could end up with a submodule that works properly when the code that uses it loads http.client
possibly by using a library that happens to use it but will otherwise fail.
这将我带回到我的初始点 - 始终显式导入子模块.
This takes me back to my initial point - always explicitly import submodules.
相关文章