notebook.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. """
  2. IPython/Jupyter Notebook progressbar decorator for iterators.
  3. Includes a default `range` iterator printing to `stderr`.
  4. Usage:
  5. >>> from tqdm.notebook import trange, tqdm
  6. >>> for i in trange(10):
  7. ... ...
  8. """
  9. # import compatibility functions and utilities
  10. import re
  11. import sys
  12. from html import escape
  13. from weakref import proxy
  14. # to inherit from the tqdm class
  15. from .std import tqdm as std_tqdm
  16. if True: # pragma: no cover
  17. # import IPython/Jupyter base widget and display utilities
  18. IPY = 0
  19. try: # IPython 4.x
  20. import ipywidgets
  21. IPY = 4
  22. except ImportError: # IPython 3.x / 2.x
  23. IPY = 32
  24. import warnings
  25. with warnings.catch_warnings():
  26. warnings.filterwarnings(
  27. 'ignore', message=".*The `IPython.html` package has been deprecated.*")
  28. try:
  29. import IPython.html.widgets as ipywidgets # NOQA: F401
  30. except ImportError:
  31. pass
  32. try: # IPython 4.x / 3.x
  33. if IPY == 32:
  34. from IPython.html.widgets import HTML
  35. from IPython.html.widgets import FloatProgress as IProgress
  36. from IPython.html.widgets import HBox
  37. IPY = 3
  38. else:
  39. from ipywidgets import HTML
  40. from ipywidgets import FloatProgress as IProgress
  41. from ipywidgets import HBox
  42. except ImportError:
  43. try: # IPython 2.x
  44. from IPython.html.widgets import HTML
  45. from IPython.html.widgets import ContainerWidget as HBox
  46. from IPython.html.widgets import FloatProgressWidget as IProgress
  47. IPY = 2
  48. except ImportError:
  49. IPY = 0
  50. IProgress = None
  51. HBox = object
  52. try:
  53. from IPython.display import display # , clear_output
  54. except ImportError:
  55. pass
  56. __author__ = {"github.com/": ["lrq3000", "casperdcl", "alexanderkuk"]}
  57. __all__ = ['tqdm_notebook', 'tnrange', 'tqdm', 'trange']
  58. WARN_NOIPYW = ("IProgress not found. Please update jupyter and ipywidgets."
  59. " See https://ipywidgets.readthedocs.io/en/stable"
  60. "/user_install.html")
  61. class TqdmHBox(HBox):
  62. """`ipywidgets.HBox` with a pretty representation"""
  63. def _json_(self, pretty=None):
  64. pbar = getattr(self, 'pbar', None)
  65. if pbar is None:
  66. return {}
  67. d = pbar.format_dict
  68. if pretty is not None:
  69. d["ascii"] = not pretty
  70. return d
  71. def __repr__(self, pretty=False):
  72. pbar = getattr(self, 'pbar', None)
  73. if pbar is None:
  74. return super().__repr__()
  75. return pbar.format_meter(**self._json_(pretty))
  76. def _repr_pretty_(self, pp, *_, **__):
  77. pp.text(self.__repr__(True))
  78. class tqdm_notebook(std_tqdm):
  79. """
  80. Experimental IPython/Jupyter Notebook widget using tqdm!
  81. """
  82. @staticmethod
  83. def status_printer(_, total=None, desc=None, ncols=None):
  84. """
  85. Manage the printing of an IPython/Jupyter Notebook progress bar widget.
  86. """
  87. # Fallback to text bar if there's no total
  88. # DEPRECATED: replaced with an 'info' style bar
  89. # if not total:
  90. # return super(tqdm_notebook, tqdm_notebook).status_printer(file)
  91. # fp = file
  92. # Prepare IPython progress bar
  93. if IProgress is None: # #187 #451 #558 #872
  94. raise ImportError(WARN_NOIPYW)
  95. if total:
  96. pbar = IProgress(min=0, max=total)
  97. else: # No total? Show info style bar with no progress tqdm status
  98. pbar = IProgress(min=0, max=1)
  99. pbar.value = 1
  100. pbar.bar_style = 'info'
  101. if ncols is None:
  102. pbar.layout.width = "20px"
  103. ltext = HTML()
  104. rtext = HTML()
  105. if desc:
  106. ltext.value = desc
  107. container = TqdmHBox(children=[ltext, pbar, rtext])
  108. # Prepare layout
  109. if ncols is not None: # use default style of ipywidgets
  110. # ncols could be 100, "100px", "100%"
  111. ncols = str(ncols) # ipywidgets only accepts string
  112. try:
  113. if int(ncols) > 0: # isnumeric and positive
  114. ncols += 'px'
  115. except ValueError:
  116. pass
  117. pbar.layout.flex = '2'
  118. container.layout.width = ncols
  119. container.layout.display = 'inline-flex'
  120. container.layout.flex_flow = 'row wrap'
  121. return container
  122. def display(self, msg=None, pos=None,
  123. # additional signals
  124. close=False, bar_style=None, check_delay=True):
  125. # Note: contrary to native tqdm, msg='' does NOT clear bar
  126. # goal is to keep all infos if error happens so user knows
  127. # at which iteration the loop failed.
  128. # Clear previous output (really necessary?)
  129. # clear_output(wait=1)
  130. if not msg and not close:
  131. d = self.format_dict
  132. # remove {bar}
  133. d['bar_format'] = (d['bar_format'] or "{l_bar}<bar/>{r_bar}").replace(
  134. "{bar}", "<bar/>")
  135. msg = self.format_meter(**d)
  136. ltext, pbar, rtext = self.container.children
  137. pbar.value = self.n
  138. if msg:
  139. msg = msg.replace(' ', u'\u2007') # fix html space padding
  140. # html escape special characters (like '&')
  141. if '<bar/>' in msg:
  142. left, right = map(escape, re.split(r'\|?<bar/>\|?', msg, maxsplit=1))
  143. else:
  144. left, right = '', escape(msg)
  145. # Update description
  146. ltext.value = left
  147. # never clear the bar (signal: msg='')
  148. if right:
  149. rtext.value = right
  150. # Change bar style
  151. if bar_style:
  152. # Hack-ish way to avoid the danger bar_style being overridden by
  153. # success because the bar gets closed after the error...
  154. if pbar.bar_style != 'danger' or bar_style != 'success':
  155. pbar.bar_style = bar_style
  156. # Special signal to close the bar
  157. if close and pbar.bar_style != 'danger': # hide only if no error
  158. try:
  159. self.container.close()
  160. except AttributeError:
  161. self.container.visible = False
  162. self.container.layout.visibility = 'hidden' # IPYW>=8
  163. if check_delay and self.delay > 0 and not self.displayed:
  164. display(self.container)
  165. self.displayed = True
  166. @property
  167. def colour(self):
  168. if hasattr(self, 'container'):
  169. return self.container.children[-2].style.bar_color
  170. @colour.setter
  171. def colour(self, bar_color):
  172. if hasattr(self, 'container'):
  173. self.container.children[-2].style.bar_color = bar_color
  174. def __init__(self, *args, **kwargs):
  175. """
  176. Supports the usual `tqdm.tqdm` parameters as well as those listed below.
  177. Parameters
  178. ----------
  179. display : Whether to call `display(self.container)` immediately
  180. [default: True].
  181. """
  182. kwargs = kwargs.copy()
  183. # Setup default output
  184. file_kwarg = kwargs.get('file', sys.stderr)
  185. if file_kwarg is sys.stderr or file_kwarg is None:
  186. kwargs['file'] = sys.stdout # avoid the red block in IPython
  187. # Initialize parent class + avoid printing by using gui=True
  188. kwargs['gui'] = True
  189. # convert disable = None to False
  190. kwargs['disable'] = bool(kwargs.get('disable', False))
  191. colour = kwargs.pop('colour', None)
  192. display_here = kwargs.pop('display', True)
  193. super().__init__(*args, **kwargs)
  194. if self.disable or not kwargs['gui']:
  195. self.disp = lambda *_, **__: None
  196. return
  197. # Get bar width
  198. self.ncols = '100%' if self.dynamic_ncols else kwargs.get("ncols", None)
  199. # Replace with IPython progress bar display (with correct total)
  200. unit_scale = 1 if self.unit_scale is True else self.unit_scale or 1
  201. total = self.total * unit_scale if self.total else self.total
  202. self.container = self.status_printer(self.fp, total, self.desc, self.ncols)
  203. self.container.pbar = proxy(self)
  204. self.displayed = False
  205. if display_here and self.delay <= 0:
  206. display(self.container)
  207. self.displayed = True
  208. self.disp = self.display
  209. self.colour = colour
  210. # Print initial bar state
  211. if not self.disable:
  212. self.display(check_delay=False)
  213. def __iter__(self):
  214. try:
  215. it = super().__iter__()
  216. for obj in it:
  217. # return super(tqdm...) will not catch exception
  218. yield obj
  219. # NB: except ... [ as ...] breaks IPython async KeyboardInterrupt
  220. except: # NOQA
  221. self.disp(bar_style='danger')
  222. raise
  223. # NB: don't `finally: close()`
  224. # since this could be a shared bar which the user will `reset()`
  225. def update(self, n=1):
  226. try:
  227. return super().update(n=n)
  228. # NB: except ... [ as ...] breaks IPython async KeyboardInterrupt
  229. except: # NOQA
  230. # cannot catch KeyboardInterrupt when using manual tqdm
  231. # as the interrupt will most likely happen on another statement
  232. self.disp(bar_style='danger')
  233. raise
  234. # NB: don't `finally: close()`
  235. # since this could be a shared bar which the user will `reset()`
  236. def close(self):
  237. if self.disable:
  238. return
  239. super().close()
  240. # Try to detect if there was an error or KeyboardInterrupt
  241. # in manual mode: if n < total, things probably got wrong
  242. if self.total and self.n < self.total:
  243. self.disp(bar_style='danger', check_delay=False)
  244. else:
  245. if self.leave:
  246. self.disp(bar_style='success', check_delay=False)
  247. else:
  248. self.disp(close=True, check_delay=False)
  249. def clear(self, *_, **__):
  250. pass
  251. def reset(self, total=None):
  252. """
  253. Resets to 0 iterations for repeated use.
  254. Consider combining with `leave=True`.
  255. Parameters
  256. ----------
  257. total : int or float, optional. Total to use for the new bar.
  258. """
  259. if self.disable:
  260. return super().reset(total=total)
  261. _, pbar, _ = self.container.children
  262. pbar.bar_style = ''
  263. if total is not None:
  264. pbar.max = total
  265. if not self.total and self.ncols is None: # no longer unknown total
  266. pbar.layout.width = None # reset width
  267. return super().reset(total=total)
  268. def tnrange(*args, **kwargs):
  269. """Shortcut for `tqdm.notebook.tqdm(range(*args), **kwargs)`."""
  270. return tqdm_notebook(range(*args), **kwargs)
  271. # Aliases
  272. tqdm = tqdm_notebook
  273. trange = tnrange