notebook.py 9.9 KB

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