Repository: tqdm/tqdm (Python)

Issue:
tqdm_notebook and tqdm.pandas fails when given *total* argument.
My code:

>import tqdm
df = pandas dataframe with 1000 rows
tqdm.tqdm_notebook().pandas(total="1000")
df.progress_apply(myfunction, axis=1)

The error:

>C:\pythonstuff\lib\site-packages\tqdm\_tqdm.py in inner(df, func, *args, **kwargs)
    511                     deprecated_t[0] = None
    512                 else:
--> 513                     t = tclass(*targs, total=total, **tkwargs)
    514 
    515                 # Define bar updating wrapper
TypeError: type object got multiple values for keyword argument 'total'

Without specifying *total* it seems to work, but I don't get a percentage from the output. Also there is an extra bar.

![tqdm_notebook_pandas](https://cloud.githubusercontent.com/assets/156232/24570067/903e5906-1637-11e7-82b9-e794752a32b3.gif)

'tqdm' object has no attribute 'total' when using 'disable' and multiprocessing 'Pool'
The following MWE shows how to get the error message described in the title:

```python
from tqdm import tqdm
from multiprocessing import Pool

def f(arg):
    pass

if __name__ == '__main__':
    pool = Pool()
    list(tqdm(pool.imap(f, range(100)), disable=True))
```

- tqdm Version: 4.22.0
- python 3.6.0
- Win64

It works, when the `disable=True` is removed or set to `False`. But it should also work for the disabled case I would guess.
Add `total` support to `tqdm.pandas()`
@casperdcl 
I think here it should be a warning, because normally total should be automatically computed by tqdm. and only a few cases need manually set total. Like this:
<img width="1020" alt="screen shot 2018-04-12 at 12 16 11 pm" src="https://user-images.githubusercontent.com/824529/38671319-5dd90564-3e4b-11e8-8610-dcae47d357fe.png">





Hints:
This happens with `tqdm.tqdm.pandas` as well. The issue seems to be that tqdm assumes the total count is the number of pandas columns, but `df.progress_apply(myfunction, axis=1)` is applying `myfunction` to *rows*, not *columns*.

If you attempt to manually specify the total, tqdm throws and exception because it "got multiple values for keyword argument 'total'". And if you don't manually specify a total, tqdm uses the *columns count* rather than the *row count*.

As a workaround, the deprecated `tqdm_pandas(tqdm(total=df.shape[0]))` way of doing things is working for me.
This is confirmed. Try to solve it.
yes, this is easy to solve, surprised I didn't do it already.
can you check PL #535? @casperdcl 
related: #530, #535
@casperdcl yep, maybe merge 8b0bdc6, #530, #535, #537  together
confirmed on linux py35


Patch:
diff --git a/tqdm/_main.py b/tqdm/_main.py
--- a/tqdm/_main.py
+++ b/tqdm/_main.py
@@ -128,7 +128,8 @@ def main(fp=sys.stderr):
         # sys.argv.pop(log)
         # logLevel = sys.argv.pop(log)
         logLevel = sys.argv[log + 1]
-    logging.basicConfig(level=getattr(logging, logLevel),
+    logging.basicConfig(
+        level=getattr(logging, logLevel),
         format="%(levelname)s:%(module)s:%(lineno)d:%(message)s")
     log = logging.getLogger(__name__)
 
diff --git a/tqdm/_tqdm.py b/tqdm/_tqdm.py
--- a/tqdm/_tqdm.py
+++ b/tqdm/_tqdm.py
@@ -47,14 +47,17 @@ class TqdmWarning(Warning):
     """
     def __init__(self, msg, fp_write=None, *a, **k):
         if fp_write is not None:
-            fp_write("\n" + self.__class__.__name__ + ": " + str(msg).rstrip() + '\n')
+            fp_write("\n" + self.__class__.__name__ + ": " +
+                     str(msg).rstrip() + '\n')
         else:
             super(TqdmWarning, self).__init__(msg, *a, **k)
 
+
 class TqdmExperimentalWarning(TqdmWarning, FutureWarning):
     """beta feature, unstable API and behaviour"""
     pass
 
+
 class TqdmDeprecationWarning(TqdmWarning, DeprecationWarning):
     # not suppressed if raised
     pass
@@ -542,6 +545,11 @@ def pandas(tclass, *targs, **tkwargs):
         from pandas.core.groupby import GroupBy
         from pandas.core.groupby import PanelGroupBy
         from pandas import Panel
+        try:
+            # pandas>=0.18.0
+            from pandas.core.window import _Rolling_and_Expanding
+        except ImportError:  # pragma: no cover
+            _Rolling_and_Expanding = None
 
         deprecated_t = [tkwargs.pop('deprecated_t', None)]
 
@@ -559,13 +567,15 @@ def inner(df, func, *args, **kwargs):
                 """
 
                 # Precompute total iterations
-                total = getattr(df, 'ngroups', None)
+                total = tkwargs.pop("total", getattr(df, 'ngroups', None))
                 if total is None:  # not grouped
                     if df_function == 'applymap':
                         total = df.size
                     elif isinstance(df, Series):
                         total = len(df)
-                    else:  # DataFrame or Panel
+                    elif _Rolling_and_Expanding is None or \
+                            not isinstance(df, _Rolling_and_Expanding):
+                        # DataFrame or Panel
                         axis = kwargs.get('axis', 0)
                         # when axis=0, total is shape[axis1]
                         total = df.size // df.shape[axis]
@@ -592,7 +602,7 @@ def wrapper(*args, **kwargs):
                     # it seems `pandas apply` calls `func` twice
                     # on the first column/row to decide whether it can
                     # take a fast or slow code path; so stop when t.total==t.n
-                    t.update(n=1 if t.total and t.n < t.total else 0)
+                    t.update(n=1 if not t.total or t.n < t.total else 0)
                     return func(*args, **kwargs)
 
                 # Apply the provided function (in **kwargs)
@@ -623,6 +633,9 @@ def wrapper(*args, **kwargs):
         GroupBy.progress_aggregate = inner_generator('aggregate')
         GroupBy.progress_transform = inner_generator('transform')
 
+        if _Rolling_and_Expanding is not None:  # pragma: no cover
+            _Rolling_and_Expanding.progress_apply = inner_generator()
+
     def __init__(self, iterable=None, desc=None, total=None, leave=True,
                  file=None, ncols=None, mininterval=0.1, maxinterval=10.0,
                  miniters=None, ascii=None, disable=False, unit='it',
@@ -853,7 +866,7 @@ def __len__(self):
         return self.total if self.iterable is None else \
             (self.iterable.shape[0] if hasattr(self.iterable, "shape")
              else len(self.iterable) if hasattr(self.iterable, "__len__")
-             else self.total)
+             else getattr(self, "total", None))
 
     def __enter__(self):
         return self
diff --git a/tqdm/_tqdm_notebook.py b/tqdm/_tqdm_notebook.py
--- a/tqdm/_tqdm_notebook.py
+++ b/tqdm/_tqdm_notebook.py
@@ -80,7 +80,7 @@ class tqdm_notebook(tqdm):
     """
 
     @staticmethod
-    def status_printer(_, total=None, desc=None):
+    def status_printer(_, total=None, desc=None, ncols=None):
         """
         Manage the printing of an IPython/Jupyter Notebook progress bar widget.
         """
@@ -104,6 +104,17 @@ def status_printer(_, total=None, desc=None):
         ptext = HTML()
         # Only way to place text to the right of the bar is to use a container
         container = HBox(children=[pbar, ptext])
+        # Prepare layout
+        if ncols is not None:  # use default style of ipywidgets
+            # ncols could be 100, "100px", "100%"
+            ncols = str(ncols)  # ipywidgets only accepts string
+            if ncols[-1].isnumeric():
+                # if last value is digit, assume the value is digit
+                ncols += 'px'
+            pbar.layout.flex = '2'
+            container.layout.width = ncols
+            container.layout.display = 'inline-flex'
+            container.layout.flex_flow = 'row wrap'
         display(container)
 
         def print_status(s='', close=False, bar_style=None, desc=None):
@@ -172,8 +183,13 @@ def __init__(self, *args, **kwargs):
         # Delete first pbar generated from super() (wrong total and text)
         # DEPRECATED by using gui=True
         # self.sp('', close=True)
+
+        # Get bar width
+        self.ncols = '100%' if self.dynamic_ncols else kwargs.get("ncols", None)
+
         # Replace with IPython progress bar display (with correct total)
-        self.sp = self.status_printer(self.fp, self.total, self.desc)
+        self.sp = self.status_printer(
+            self.fp, self.total, self.desc, self.ncols)
         self.desc = None  # trick to place description before the bar
 
         # Print initial bar state
@@ -186,7 +202,7 @@ def __iter__(self, *args, **kwargs):
                 # return super(tqdm...) will not catch exception
                 yield obj
         # NB: except ... [ as ...] breaks IPython async KeyboardInterrupt
-        except:
+        except:  # NOQA
             self.sp(bar_style='danger')
             raise
 


Test patch:
diff --git a/tqdm/tests/tests_pandas.py b/tqdm/tests/tests_pandas.py
--- a/tqdm/tests/tests_pandas.py
+++ b/tqdm/tests/tests_pandas.py
@@ -1,7 +1,54 @@
-from nose.plugins.skip import SkipTest
-
 from tqdm import tqdm
-from tests_tqdm import with_setup, pretest, posttest, StringIO, closing
+from tests_tqdm import with_setup, pretest, posttest, SkipTest, \
+    StringIO, closing
+
+
+@with_setup(pretest, posttest)
+def test_pandas_setup():
+    """Test tqdm.pandas()"""
+    try:
+        from numpy.random import randint
+        import pandas as pd
+    except ImportError:
+        raise SkipTest
+
+    with closing(StringIO()) as our_file:
+        tqdm.pandas(file=our_file, leave=True, ascii=True, total=123)
+        series = pd.Series(randint(0, 50, (100,)))
+        series.progress_apply(lambda x: x + 10)
+        res = our_file.getvalue()
+        assert '100/123' in res
+
+
+@with_setup(pretest, posttest)
+def test_pandas_rolling_expanding():
+    """Test pandas.(Series|DataFrame).(rolling|expanding)"""
+    try:
+        from numpy.random import randint
+        import pandas as pd
+    except ImportError:
+        raise SkipTest
+
+    with closing(StringIO()) as our_file:
+        tqdm.pandas(file=our_file, leave=True, ascii=True)
+
+        series = pd.Series(randint(0, 50, (123,)))
+        res1 = series.rolling(10).progress_apply(lambda x: 1)
+        res2 = series.rolling(10).apply(lambda x: 1)
+        assert res1.equals(res2)
+
+        res3 = series.expanding(10).progress_apply(lambda x: 2)
+        res4 = series.expanding(10).apply(lambda x: 2)
+        assert res3.equals(res4)
+
+        expects = ['114it']  # 123-10+1
+        for exres in expects:
+            our_file.seek(0)
+            if our_file.getvalue().count(exres) < 2:
+                our_file.seek(0)
+                raise AssertionError(
+                    "\nExpected:\n{0}\nIn:\n{1}\n".format(
+                        exres + " at least twice.", our_file.read()))
 
 
 @with_setup(pretest, posttest)
diff --git a/tqdm/tests/tests_synchronisation.py b/tqdm/tests/tests_synchronisation.py
--- a/tqdm/tests/tests_synchronisation.py
+++ b/tqdm/tests/tests_synchronisation.py
@@ -1,11 +1,11 @@
 from __future__ import division
-from tqdm import tqdm
-from tests_tqdm import with_setup, pretest, posttest, StringIO, closing
+from tqdm import tqdm, TMonitor
+from tests_tqdm import with_setup, pretest, posttest, SkipTest, \
+    StringIO, closing
 from tests_tqdm import DiscreteTimer, cpu_timify
 
 from time import sleep
 from threading import Event
-from tqdm import TMonitor
 
 
 class FakeSleep(object):
@@ -37,6 +37,10 @@ def create_fake_sleep_event():
     return create_fake_sleep_event
 
 
+def incr(x):
+    return x + 1
+
+
 @with_setup(pretest, posttest)
 def test_monitor_thread():
     """Test dummy monitoring thread"""
@@ -162,3 +166,16 @@ def test_monitoring_multi():
     # Check that class var monitor is deleted if no instance left
     tqdm.monitor_interval = 10
     assert tqdm.monitor is None
+
+
+@with_setup(pretest, posttest)
+def test_imap():
+    """Test multiprocessing.Pool"""
+    try:
+        from multiprocessing import Pool
+    except ImportError:
+        raise SkipTest
+
+    pool = Pool()
+    res = list(tqdm(pool.imap(incr, range(100)), disable=True))
+    assert res[-1] == 100