5

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?

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()

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()

A file called GUI.py imports this file with: from lib.filesearch import * (the line of code I mentioned resides within the filesearch file.)

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:

From filesearch.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

From 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'

Unlike this question I only have one version of python installed. Namely, Python34.

Community
  • 1
  • 1
Dzhao
  • 683
  • 1
  • 9
  • 22
  • 1
    Try adding a simple debug `print(tk)` to see what it actually imports. Initially differing python paths come to mind. – Ilja Everilä Mar 22 '16 at 19:44
  • @Ilja from IDLE: `` and from command line: `` – Dzhao Mar 22 '16 at 19:47
  • That seems ok, back to the drawing board. – Ilja Everilä Mar 22 '16 at 19:48
  • 2
    you need to import the submodule with `import tkinter.filedialog` to load it, the reason you don't in IDLE is because idle already did import it because it is running on tkinter – Tadhg McDonald-Jensen Mar 22 '16 at 19:53
  • @TadhgMcDonald-Jensen Well that solves it. Is this also when I import tkinter I can't use the ttk submodule? – Dzhao Mar 22 '16 at 19:56
  • 2
    take a look at [the docs](https://docs.python.org/2/tutorial/modules.html#importing-from-a-package) about packages defining a `__all__` property in their `__init__.py`, it defines which submodules to load when the top level module is imported, some packages like `numpy` or `matplotlib` import **lots** of submodules which is why they take so long to load but other packages like `tkinter` and `PIL` will only load the ones you explicitly ask them to. – Tadhg McDonald-Jensen Mar 22 '16 at 20:42
  • 4
    This is, in a sense, a bug in IDLE that its internal operation is exposed this way. I hope to fix this someday by refactoring several modules so tkinter is not even imported into the user process before user code runs. – Terry Jan Reedy Mar 23 '16 at 00:00

2 Answers2

15

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.

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

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 was effectively a bug in IDLE.

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

And in 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 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)


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'>

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'

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.

Tadhg McDonald-Jensen
  • 20,699
  • 5
  • 35
  • 59
5

Actually it's true that the module has no attribute filedialog, it's a submodule and you should import it as import tkinter.filedialog before using it. You can use tk.filedialog without explicitly importing filedialog in IDLE because it's already imported.

import sys
sys.modules['tkinter.filedialog']

The above code will raise a KeyError in a standard python interpreter but it will return something like <module 'tkinter.filedialog' from '/usr/lib/python3.5/tkinter/filedialog.py'> in IDLE.

Stop harming Monica
  • 12,141
  • 1
  • 36
  • 56