Skip to content

Commit d60a728

Browse files
authored
Merge pull request nltk#3348 from samertm/fixcrash
Only import tkinter if a GUI is needed
2 parents 895a1a9 + 8fe3348 commit d60a728

File tree

4 files changed

+72
-58
lines changed

4 files changed

+72
-58
lines changed

AUTHORS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,7 @@
304304
- Ron Urbach <https://github.com/sharpblade4>
305305
- Vivek Kalyan <https://github.com/vivekkalyan>
306306
- Tom Strange https://github.com/strangetom
307+
- Samer Masterson <https://github.com/samertm>
307308
- William LaCroix <https://github.com/WilliamPLaCroix>
308309
- Peter de Blanc <https://github.com/pdeblanc>
309310

nltk/__init__.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"""
2020

2121
import os
22+
import importlib
2223

2324
# //////////////////////////////////////////////////////
2425
# Metadata
@@ -169,7 +170,6 @@ def _fake_Popen(*args, **kwargs):
169170
toolbox = lazyimport.LazyModule("toolbox", locals(), globals())
170171

171172
# Optional loading
172-
173173
try:
174174
import numpy
175175
except ImportError:
@@ -179,11 +179,10 @@ def _fake_Popen(*args, **kwargs):
179179

180180
from nltk.downloader import download, download_shell
181181

182-
try:
183-
import tkinter
184-
except ImportError:
185-
pass
186-
else:
182+
# Check if tkinter exists without importing it to avoid crashes after
183+
# forks on macOS. Only nltk.app, nltk.draw, and demo modules should
184+
# have top-level tkinter imports. See #2949 for more details.
185+
if importlib.util.find_spec('tkinter'):
187186
try:
188187
from nltk.downloader import download_gui
189188
except RuntimeError as e:

nltk/downloader.py

Lines changed: 59 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -171,18 +171,6 @@
171171
import zipfile
172172
from hashlib import md5
173173
from xml.etree import ElementTree
174-
175-
try:
176-
TKINTER = True
177-
from tkinter import Button, Canvas, Entry, Frame, IntVar, Label, Menu, TclError, Tk
178-
from tkinter.messagebox import showerror
179-
180-
from nltk.draw.table import Table
181-
from nltk.draw.util import ShowText
182-
except ImportError:
183-
TKINTER = False
184-
TclError = ValueError
185-
186174
from urllib.error import HTTPError, URLError
187175
from urllib.request import urlopen
188176

@@ -1103,17 +1091,27 @@ def _set_download_dir(self, download_dir):
11031091
# /////////////////////////////////////////////////////////////////
11041092

11051093
def _interactive_download(self):
1094+
# Only import tkinter if the user has indicated that they
1095+
# want to draw a UI. See issue #2949 for more info.
1096+
if os.environ.get('NLTK_DOWNLOADER_FORCE_INTERACTIVE_SHELL', 'false').lower() == 'true':
1097+
DownloaderShell(self).run()
1098+
return
1099+
11061100
# Try the GUI first; if that doesn't work, try the simple
11071101
# interactive shell.
1108-
if TKINTER:
1109-
try:
1110-
DownloaderGUI(self).mainloop()
1111-
except TclError:
1112-
DownloaderShell(self).run()
1113-
else:
1102+
try:
1103+
import tkinter
1104+
except ImportError:
1105+
DownloaderShell(self).run()
1106+
return
1107+
1108+
try:
1109+
DownloaderGUI(self).mainloop()
1110+
except tkinter.TclError:
11141111
DownloaderShell(self).run()
11151112

11161113

1114+
11171115
class DownloaderShell:
11181116
def __init__(self, dataserver):
11191117
self._ds = dataserver
@@ -1370,6 +1368,10 @@ class DownloaderGUI:
13701368
# /////////////////////////////////////////////////////////////////
13711369

13721370
def __init__(self, dataserver, use_threads=True):
1371+
# Only import tkinter if the user has indicated that they
1372+
# want to draw a UI. See issue #2949 for more info.
1373+
import tkinter
1374+
from tkinter.messagebox import showerror
13731375
self._ds = dataserver
13741376
self._use_threads = use_threads
13751377

@@ -1388,7 +1390,7 @@ def __init__(self, dataserver, use_threads=True):
13881390
self._log("NLTK Downloader Started!")
13891391

13901392
# Create the main window.
1391-
top = self.top = Tk()
1393+
top = self.top = tkinter.Tk()
13921394
top.geometry("+50+50")
13931395
top.title("NLTK Downloader")
13941396
top.configure(background=self._BACKDROP_COLOR[1])
@@ -1428,23 +1430,28 @@ def _log(self, msg):
14281430
# /////////////////////////////////////////////////////////////////
14291431

14301432
def _init_widgets(self):
1433+
# Only import tkinter if the user has indicated that they
1434+
# want to draw a UI. See issue #2949 for more info.
1435+
import tkinter
1436+
from nltk.draw.table import Table
1437+
14311438
# Create the top-level frame structures
1432-
f1 = Frame(self.top, relief="raised", border=2, padx=8, pady=0)
1439+
f1 = tkinter.Frame(self.top, relief="raised", border=2, padx=8, pady=0)
14331440
f1.pack(sid="top", expand=True, fill="both")
14341441
f1.grid_rowconfigure(2, weight=1)
14351442
f1.grid_columnconfigure(0, weight=1)
1436-
Frame(f1, height=8).grid(column=0, row=0) # spacer
1437-
tabframe = Frame(f1)
1443+
tkinter.Frame(f1, height=8).grid(column=0, row=0) # spacer
1444+
tabframe = tkinter.Frame(f1)
14381445
tabframe.grid(column=0, row=1, sticky="news")
1439-
tableframe = Frame(f1)
1446+
tableframe = tkinter.Frame(f1)
14401447
tableframe.grid(column=0, row=2, sticky="news")
1441-
buttonframe = Frame(f1)
1448+
buttonframe = tkinter.Frame(f1)
14421449
buttonframe.grid(column=0, row=3, sticky="news")
1443-
Frame(f1, height=8).grid(column=0, row=4) # spacer
1444-
infoframe = Frame(f1)
1450+
tkinter.Frame(f1, height=8).grid(column=0, row=4) # spacer
1451+
infoframe = tkinter.Frame(f1)
14451452
infoframe.grid(column=0, row=5, sticky="news")
1446-
Frame(f1, height=8).grid(column=0, row=6) # spacer
1447-
progressframe = Frame(
1453+
tkinter.Frame(f1, height=8).grid(column=0, row=6) # spacer
1454+
progressframe = tkinter.Frame(
14481455
self.top, padx=3, pady=3, background=self._BACKDROP_COLOR[1]
14491456
)
14501457
progressframe.pack(side="bottom", fill="x")
@@ -1455,7 +1462,7 @@ def _init_widgets(self):
14551462
self._tab_names = ["Collections", "Corpora", "Models", "All Packages"]
14561463
self._tabs = {}
14571464
for i, tab in enumerate(self._tab_names):
1458-
label = Label(tabframe, text=tab, font=self._TAB_FONT)
1465+
label = tkinter.Label(tabframe, text=tab, font=self._TAB_FONT)
14591466
label.pack(side="left", padx=((i + 1) % 2) * 10)
14601467
label.bind("<Button-1>", self._select_tab)
14611468
self._tabs[tab.lower()] = label
@@ -1492,8 +1499,8 @@ def _init_widgets(self):
14921499
]
14931500
self._info = {}
14941501
for i, (key, label, callback) in enumerate(info):
1495-
Label(infoframe, text=label).grid(column=0, row=i, sticky="e")
1496-
entry = Entry(
1502+
tkinter.Label(infoframe, text=label).grid(column=0, row=i, sticky="e")
1503+
entry = tkinter.Entry(
14971504
infoframe,
14981505
font="courier",
14991506
relief="groove",
@@ -1510,23 +1517,23 @@ def _init_widgets(self):
15101517
self.top.bind("<Button-1>", self._info_save)
15111518

15121519
# Create Download & Refresh buttons.
1513-
self._download_button = Button(
1520+
self._download_button = tkinter.Button(
15141521
buttonframe, text="Download", command=self._download, width=8
15151522
)
15161523
self._download_button.pack(side="left")
1517-
self._refresh_button = Button(
1524+
self._refresh_button = tkinter.Button(
15181525
buttonframe, text="Refresh", command=self._refresh, width=8
15191526
)
15201527
self._refresh_button.pack(side="right")
15211528

15221529
# Create Progress bar
1523-
self._progresslabel = Label(
1530+
self._progresslabel = tkinter.Label(
15241531
progressframe,
15251532
text="",
15261533
foreground=self._BACKDROP_COLOR[0],
15271534
background=self._BACKDROP_COLOR[1],
15281535
)
1529-
self._progressbar = Canvas(
1536+
self._progressbar = tkinter.Canvas(
15301537
progressframe,
15311538
width=200,
15321539
height=16,
@@ -1539,9 +1546,11 @@ def _init_widgets(self):
15391546
self._progresslabel.pack(side="left")
15401547

15411548
def _init_menu(self):
1542-
menubar = Menu(self.top)
1549+
import tkinter
1550+
1551+
menubar = tkinter.Menu(self.top)
15431552

1544-
filemenu = Menu(menubar, tearoff=0)
1553+
filemenu = tkinter.Menu(menubar, tearoff=0)
15451554
filemenu.add_command(
15461555
label="Download", underline=0, command=self._download, accelerator="Return"
15471556
)
@@ -1567,9 +1576,9 @@ def _init_menu(self):
15671576
# Create a menu to control which columns of the table are
15681577
# shown. n.b.: we never hide the first two columns (mark and
15691578
# identifier).
1570-
viewmenu = Menu(menubar, tearoff=0)
1579+
viewmenu = tkinter.Menu(menubar, tearoff=0)
15711580
for column in self._table.column_names[2:]:
1572-
var = IntVar(self.top)
1581+
var = tkinter.IntVar(self.top)
15731582
assert column not in self._column_vars
15741583
self._column_vars[column] = var
15751584
if column in self.INITIAL_COLUMNS:
@@ -1582,7 +1591,7 @@ def _init_menu(self):
15821591
# Create a sort menu
15831592
# [xx] this should be selectbuttons; and it should include
15841593
# reversed sorts as options.
1585-
sortmenu = Menu(menubar, tearoff=0)
1594+
sortmenu = tkinter.Menu(menubar, tearoff=0)
15861595
for column in self._table.column_names[1:]:
15871596
sortmenu.add_command(
15881597
label="Sort by %s" % column,
@@ -1597,7 +1606,7 @@ def _init_menu(self):
15971606
)
15981607
menubar.add_cascade(label="Sort", underline=0, menu=sortmenu)
15991608

1600-
helpmenu = Menu(menubar, tearoff=0)
1609+
helpmenu = tkinter.Menu(menubar, tearoff=0)
16011610
helpmenu.add_command(label="About", underline=0, command=self.about)
16021611
helpmenu.add_command(
16031612
label="Instructions", underline=0, command=self.help, accelerator="F1"
@@ -1615,6 +1624,7 @@ def _select_columns(self):
16151624
self._table.hide_column(column)
16161625

16171626
def _refresh(self):
1627+
from tkinter.messagebox import showerror
16181628
self._ds.clear_status_cache()
16191629
try:
16201630
self._fill_table()
@@ -1661,6 +1671,7 @@ def _table_reprfunc(self, row, col, val):
16611671
return " %s" % val
16621672

16631673
def _set_url(self, url):
1674+
from tkinter.messagebox import showerror
16641675
if url == self._ds.url:
16651676
return
16661677
try:
@@ -1671,6 +1682,7 @@ def _set_url(self, url):
16711682
self._show_info()
16721683

16731684
def _set_download_dir(self, download_dir):
1685+
from tkinter.messagebox import showerror
16741686
if self._ds.download_dir == download_dir:
16751687
return
16761688
# check if the dir exists, and if not, ask if we should create it?
@@ -1696,6 +1708,7 @@ def _show_info(self):
16961708
entry["state"] = "disabled"
16971709

16981710
def _prev_tab(self, *e):
1711+
from tkinter.messagebox import showerror
16991712
for i, tab in enumerate(self._tab_names):
17001713
if tab.lower() == self._tab and i > 0:
17011714
self._tab = self._tab_names[i - 1].lower()
@@ -1707,6 +1720,7 @@ def _prev_tab(self, *e):
17071720
showerror("Error connecting to server", e.reason)
17081721

17091722
def _next_tab(self, *e):
1723+
from tkinter.messagebox import showerror
17101724
for i, tab in enumerate(self._tab_names):
17111725
if tab.lower() == self._tab and i < (len(self._tabs) - 1):
17121726
self._tab = self._tab_names[i + 1].lower()
@@ -1718,6 +1732,7 @@ def _next_tab(self, *e):
17181732
showerror("Error connecting to server", e.reason)
17191733

17201734
def _select_tab(self, event):
1735+
from tkinter.messagebox import showerror
17211736
self._tab = event.widget["text"].lower()
17221737
try:
17231738
self._fill_table()
@@ -1884,6 +1899,7 @@ def _table_mark(self, *e):
18841899
self._table.select(delta=1)
18851900

18861901
def _show_log(self):
1902+
from nltk.draw.util import ShowText
18871903
text = "\n".join(self._log_messages)
18881904
ShowText(self.top, "NLTK Downloader Log", text)
18891905

@@ -1966,6 +1982,7 @@ def mainloop(self, *args, **kwargs):
19661982
)
19671983

19681984
def help(self, *e):
1985+
from nltk.draw.util import ShowText
19691986
# The default font's not very legible; try using 'fixed' instead.
19701987
try:
19711988
ShowText(
@@ -1979,6 +1996,7 @@ def help(self, *e):
19791996
ShowText(self.top, "Help: NLTK Downloader", self.HELP.strip(), width=75)
19801997

19811998
def about(self, *e):
1999+
from nltk.draw.util import ShowText
19822000
ABOUT = "NLTK Downloader\n" + "Written by Edward Loper"
19832001
TITLE = "About: NLTK Downloader"
19842002
try:

nltk/sem/drt.py

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,17 +37,7 @@
3737
is_indvar,
3838
unique_variable,
3939
)
40-
41-
# Import Tkinter-based modules if they are available
42-
try:
43-
from tkinter import Canvas, Tk
44-
from tkinter.font import Font
45-
46-
from nltk.util import in_idle
47-
48-
except ImportError:
49-
# No need to print a warning here, nltk.draw has already printed one.
50-
pass
40+
from nltk.util import in_idle
5141

5242

5343
class DrtTokens(Tokens):
@@ -1105,6 +1095,12 @@ def __init__(self, drs, size_canvas=True, canvas=None):
11051095
"""
11061096
master = None
11071097
if not canvas:
1098+
1099+
# Only import tkinter if the user has indicated that they
1100+
# want to draw a UI. See issue #2949 for more info.
1101+
from tkinter import Canvas, Tk
1102+
from tkinter.font import Font
1103+
11081104
master = Tk()
11091105
master.title("DRT")
11101106

0 commit comments

Comments
 (0)