Repository: numpy/numpy

Issue:
BUG: polynomial: UnboundLocalError: local variable 'quo' referenced before assignment
An error was reported on stackoverflow (http://stackoverflow.com/questions/23186008/numpy-namespace-issue).  My answer, repeated here, describes the problem.

In the implementation of `__rtruediv__` at https://github.com/numpy/numpy/blob/master/numpy/polynomial/_polybase.py#L421, you can see that if `len(self.coef)` is not 1, the statement

```
    return self.__class__(quo, self.domain, self.window)
```

is executed, but `quo` is not defined.

Here's a simple example that generates the error:

```
In [1]: import numpy.polynomial.polynomial as npp

In [2]: p1 = npp.Polynomial([1,2])

In [3]: p2 = npp.Polynomial([3,4])

In [4]: npp.polydiv(p1, p2)
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
<ipython-input-4-00c25f56fb20> in <module>()
----> 1 npp.polydiv(p1, p2)

    /Users/warren/anaconda/lib/python2.7/site-packages/numpy/polynomial/polynomial.pyc in polydiv(c1, c2)
    406     len2 = len(c2)
    407     if len2 == 1 :
--> 408         return c1/c2[-1], c1[:1]*0
    409     elif len1 < len2 :
    410         return c1[:1]*0, c1

/Users/warren/anaconda/lib/python2.7/site-packages/numpy/polynomial/polynomial.pyc in __rtruediv__(self, other)

UnboundLocalError: local variable 'quo' referenced before assignment
```



Discussion:
That the example works is a bit of an accident ;) The function call treats the division as two degree 0 polynomials with polynomial coefficients and, because the `polynomial.py` file has `from __future__ import division`, tries true division of the coefficients. The more direct test is `operator.truediv(p1, p2)`, or just `p1 / p2` in Python 3.


Resolving patch:
diff --git a/numpy/polynomial/_polybase.py b/numpy/polynomial/_polybase.py
--- a/numpy/polynomial/_polybase.py
+++ b/numpy/polynomial/_polybase.py
@@ -9,6 +9,7 @@
 from __future__ import division, absolute_import, print_function
 
 from abc import ABCMeta, abstractmethod, abstractproperty
+from numbers import Number
 
 import numpy as np
 from . import polyutils as pu
@@ -206,6 +207,42 @@ def has_sametype(self, other):
         """
         return isinstance(other, self.__class__)
 
+    def _get_coefficients(self, other):
+        """Interpret other as polynomial coefficients.
+
+        The `other` argument is checked to see if it is of the same
+        class as self with identical domain and window. If so,
+        return its coefficients, otherwise return `other`.
+
+        .. versionadded:: 1.9.0
+
+        Parameters
+        ----------
+        other : anything
+            Object to be checked.
+
+        Returns
+        -------
+        coef:
+            The coefficients of`other` if it is a compatible instance,
+            of ABCPolyBase, otherwise `other`.
+
+        Raises
+        ------
+        TypeError:
+            When `other` is an incompatible instance of ABCPolyBase.
+
+        """
+        if isinstance(other, ABCPolyBase):
+            if not isinstance(other, self.__class__):
+                raise TypeError("Polynomial types differ")
+            elif not np.all(self.domain == other.domain):
+                raise TypeError("Domains differ")
+            elif not np.all(self.window == other.window):
+                raise TypeError("Windows differ")
+            return other.coef
+        return other
+
     def __init__(self, coef, domain=None, window=None):
         [coef] = pu.as_series([coef], trim=False)
         self.coef = coef
@@ -270,54 +307,33 @@ def __pos__(self):
         return self
 
     def __add__(self, other):
-        if isinstance(other, ABCPolyBase):
-            if not self.has_sametype(other):
-                raise TypeError("Polynomial types differ")
-            elif not self.has_samedomain(other):
-                raise TypeError("Domains differ")
-            elif not self.has_samewindow(other):
-                raise TypeError("Windows differ")
-            else:
-                coef = self._add(self.coef, other.coef)
-        else:
-            try:
-                coef = self._add(self.coef, other)
-            except:
-                return NotImplemented
+        try:
+            othercoef = self._get_coefficients(other)
+            coef = self._add(self.coef, othercoef)
+        except TypeError as e:
+            raise e
+        except:
+            return NotImplemented
         return self.__class__(coef, self.domain, self.window)
 
     def __sub__(self, other):
-        if isinstance(other, ABCPolyBase):
-            if not self.has_sametype(other):
-                raise TypeError("Polynomial types differ")
-            elif not self.has_samedomain(other):
-                raise TypeError("Domains differ")
-            elif not self.has_samewindow(other):
-                raise TypeError("Windows differ")
-            else:
-                coef = self._sub(self.coef, other.coef)
-        else:
-            try:
-                coef = self._sub(self.coef, other)
-            except:
-                return NotImplemented
+        try:
+            othercoef = self._get_coefficients(other)
+            coef = self._sub(self.coef, othercoef)
+        except TypeError as e:
+            raise e
+        except:
+            return NotImplemented
         return self.__class__(coef, self.domain, self.window)
 
     def __mul__(self, other):
-        if isinstance(other, ABCPolyBase):
-            if not self.has_sametype(other):
-                raise TypeError("Polynomial types differ")
-            elif not self.has_samedomain(other):
-                raise TypeError("Domains differ")
-            elif not self.has_samewindow(other):
-                raise TypeError("Windows differ")
-            else:
-                coef = self._mul(self.coef, other.coef)
-        else:
-            try:
-                coef = self._mul(self.coef, other)
-            except:
-                return NotImplemented
+        try:
+            othercoef = self._get_coefficients(other)
+            coef = self._mul(self.coef, othercoef)
+        except TypeError as e:
+            raise e
+        except:
+            return NotImplemented
         return self.__class__(coef, self.domain, self.window)
 
     def __div__(self, other):
@@ -325,73 +341,42 @@ def __div__(self, other):
         return self.__floordiv__(other)
 
     def __truediv__(self, other):
-        # there is no true divide if the rhs is not a scalar, although it
+        # there is no true divide if the rhs is not a Number, although it
         # could return the first n elements of an infinite series.
         # It is hard to see where n would come from, though.
-        if np.isscalar(other):
-            # this might be overly restrictive
-            coef = self.coef/other
-            return self.__class__(coef, self.domain, self.window)
-        else:
-            return NotImplemented
+        if not isinstance(other, Number) or isinstance(other, bool):
+            form = "unsupported types for true division: '%s', '%s'"
+            raise TypeError(form % (type(self), type(other)))
+        return self.__floordiv__(other)
 
     def __floordiv__(self, other):
-        if isinstance(other, ABCPolyBase):
-            if not self.has_sametype(other):
-                raise TypeError("Polynomial types differ")
-            elif not self.has_samedomain(other):
-                raise TypeError("Domains differ")
-            elif not self.has_samewindow(other):
-                raise TypeError("Windows differ")
-            else:
-                quo, rem = self._div(self.coef, other.coef)
-        else:
-            try:
-                quo, rem = self._div(self.coef, other)
-            except:
-                return NotImplemented
-        return self.__class__(quo, self.domain, self.window)
+        res = self.__divmod__(other)
+        if res is NotImplemented:
+            return res
+        return res[0]
 
     def __mod__(self, other):
-        if isinstance(other, ABCPolyBase):
-            if not self.has_sametype(other):
-                raise TypeError("Polynomial types differ")
-            elif not self.has_samedomain(other):
-                raise TypeError("Domains differ")
-            elif not self.has_samewindow(other):
-                raise TypeError("Windows differ")
-            else:
-                quo, rem = self._div(self.coef, other.coef)
-        else:
-            try:
-                quo, rem = self._div(self.coef, other)
-            except:
-                return NotImplemented
-        return self.__class__(rem, self.domain, self.window)
+        res = self.__divmod__(other)
+        if res is NotImplemented:
+            return res
+        return res[1]
 
     def __divmod__(self, other):
-        if isinstance(other, self.__class__):
-            if not self.has_samedomain(other):
-                raise TypeError("Domains are not equal")
-            elif not self.has_samewindow(other):
-                raise TypeError("Windows are not equal")
-            else:
-                quo, rem = self._div(self.coef, other.coef)
-        else:
-            try:
-                quo, rem = self._div(self.coef, other)
-            except:
-                return NotImplemented
+        try:
+            othercoef = self._get_coefficients(other)
+            quo, rem = self._div(self.coef, othercoef)
+        except (TypeError, ZeroDivisionError) as e:
+            raise e
+        except:
+            return NotImplemented
         quo = self.__class__(quo, self.domain, self.window)
         rem = self.__class__(rem, self.domain, self.window)
         return quo, rem
 
     def __pow__(self, other):
-        try:
-            coef = self._pow(self.coef, other, maxpower = self.maxpower)
-        except:
-            raise
-        return self.__class__(coef, self.domain, self.window)
+        coef = self._pow(self.coef, other, maxpower = self.maxpower)
+        res = self.__class__(coef, self.domain, self.window)
+        return res
 
     def __radd__(self, other):
         try:
@@ -419,33 +404,27 @@ def __rdiv__(self, other):
         return self.__rfloordiv__(other)
 
     def __rtruediv__(self, other):
-        # there is no true divide if the rhs is not a scalar, although it
-        # could return the first n elements of an infinite series.
-        # It is hard to see where n would come from, though.
-        if len(self.coef) == 1:
-            try:
-                quo, rem = self._div(other, self.coef[0])
-            except:
-                return NotImplemented
-        return self.__class__(quo, self.domain, self.window)
+        # An instance of ABCPolyBase is not considered a
+        # Number.
+        return NotImplemented
 
     def __rfloordiv__(self, other):
-        try:
-            quo, rem = self._div(other, self.coef)
-        except:
-            return NotImplemented
-        return self.__class__(quo, self.domain, self.window)
+        res = self.__rdivmod__(other)
+        if res is NotImplemented:
+            return res
+        return res[0]
 
     def __rmod__(self, other):
-        try:
-            quo, rem = self._div(other, self.coef)
-        except:
-            return NotImplemented
-        return self.__class__(rem, self.domain, self.window)
+        res = self.__rdivmod__(other)
+        if res is NotImplemented:
+            return res
+        return res[1]
 
     def __rdivmod__(self, other):
         try:
             quo, rem = self._div(other, self.coef)
+        except ZeroDivisionError as e:
+            raise e
         except:
             return NotImplemented
         quo = self.__class__(quo, self.domain, self.window)
@@ -456,10 +435,10 @@ def __rdivmod__(self, other):
     # some augmented arithmetic operations could be added here
 
     def __eq__(self, other):
-        res = isinstance(other, self.__class__) and\
-              self.has_samecoef(other) and \
-              self.has_samedomain(other) and\
-              self.has_samewindow(other)
+        res = (isinstance(other, self.__class__) and
+               np.all(self.domain == other.domain) and
+               np.all(self.window == other.window) and
+               np.all(self.coef == other.coef))
         return res
 
     def __ne__(self, other):
@@ -813,7 +792,7 @@ class domain in NumPy 1.4 and ``None`` in later versions.
         """
         if domain is None:
             domain = pu.getdomain(x)
-        elif domain == []:
+        elif isinstance(domain, list) and len(domain) == 0:
             domain = cls.domain
 
         if window is None:
@@ -857,7 +836,7 @@ def fromroots(cls, roots, domain=[], window=None):
         [roots] = pu.as_series([roots], trim=False)
         if domain is None:
             domain = pu.getdomain(roots)
-        elif domain == []:
+        elif isinstance(domain, list) and len(domain) == 0:
             domain = cls.domain
 
         if window is None:
diff --git a/numpy/polynomial/polytemplate.py b/numpy/polynomial/polytemplate.py
--- a/numpy/polynomial/polytemplate.py
+++ b/numpy/polynomial/polytemplate.py
@@ -14,6 +14,7 @@
 import string
 import sys
 import warnings
+from number import Number
 
 from numpy import ModuleDeprecationWarning
 
@@ -287,15 +288,13 @@ def __div__(self, other):
         return self.__floordiv__(other)
 
     def __truediv__(self, other) :
-        # there is no true divide if the rhs is not a scalar, although it
+        # there is no true divide if the rhs is not a Number, although it
         # could return the first n elements of an infinite series.
         # It is hard to see where n would come from, though.
-        if np.isscalar(other) :
-            # this might be overly restrictive
-            coef = self.coef/other
-            return self.__class__(coef, self.domain, self.window)
-        else :
-            return NotImplemented
+        if not isinstance(other, Number) or isinstance(other, bool):
+            form = "unsupported types for true division: '%s', '%s'"
+            raise TypeError(form % (type(self), type(other)))
+        return self.__floordiv__(other)
 
     def __floordiv__(self, other) :
         """Returns the quotient."""
@@ -384,20 +383,14 @@ def __rdiv__(self, other):
         return self.__rfloordiv__(other)
 
     def __rtruediv__(self, other) :
-        # there is no true divide if the rhs is not a scalar, although it
-        # could return the first n elements of an infinite series.
-        # It is hard to see where n would come from, though.
-        if len(self.coef) == 1 :
-            try :
-                quo, rem = ${nick}div(other, self.coef[0])
-            except :
-                return NotImplemented
-        return self.__class__(quo, self.domain, self.window)
+        # An instance of PolyBase is not considered a
+        # Number.
+        return NotImplemented
 
     def __rfloordiv__(self, other) :
         try :
             quo, rem = ${nick}div(other, self.coef)
-        except :
+        except:
             return NotImplemented
         return self.__class__(quo, self.domain, self.window)
 


Test patch:
diff --git a/numpy/polynomial/tests/test_classes.py b/numpy/polynomial/tests/test_classes.py
--- a/numpy/polynomial/tests/test_classes.py
+++ b/numpy/polynomial/tests/test_classes.py
@@ -5,6 +5,9 @@
 """
 from __future__ import division, absolute_import, print_function
 
+import operator as op
+from numbers import Number
+
 import numpy as np
 from numpy.polynomial import (
     Polynomial, Legendre, Chebyshev, Laguerre,
@@ -13,6 +16,7 @@
     TestCase, assert_almost_equal, assert_raises,
     assert_equal, assert_, run_module_suite, dec)
 from numpy.testing.noseclasses import KnownFailure
+from numpy.compat import long
 
 
 classes = (
@@ -37,6 +41,7 @@ def test_class_methods():
         yield check_sub, Poly
         yield check_mul, Poly
         yield check_floordiv, Poly
+        yield check_truediv, Poly
         yield check_mod, Poly
         yield check_divmod, Poly
         yield check_pow, Poly
@@ -221,12 +226,12 @@ def check_add(Poly):
     assert_poly_almost_equal(tuple(c2) + p1, p3)
     assert_poly_almost_equal(p1 + np.array(c2), p3)
     assert_poly_almost_equal(np.array(c2) + p1, p3)
-    assert_raises(TypeError, p1.__add__, Poly([0], domain=Poly.domain + 1))
-    assert_raises(TypeError, p1.__add__, Poly([0], window=Poly.window + 1))
+    assert_raises(TypeError, op.add, p1, Poly([0], domain=Poly.domain + 1))
+    assert_raises(TypeError, op.add, p1, Poly([0], window=Poly.window + 1))
     if Poly is Polynomial:
-        assert_raises(TypeError, p1.__add__, Chebyshev([0]))
+        assert_raises(TypeError, op.add, p1, Chebyshev([0]))
     else:
-        assert_raises(TypeError, p1.__add__, Polynomial([0]))
+        assert_raises(TypeError, op.add, p1, Polynomial([0]))
 
 
 def check_sub(Poly):
@@ -243,12 +248,12 @@ def check_sub(Poly):
     assert_poly_almost_equal(tuple(c2) - p1, -p3)
     assert_poly_almost_equal(p1 - np.array(c2), p3)
     assert_poly_almost_equal(np.array(c2) - p1, -p3)
-    assert_raises(TypeError, p1.__sub__, Poly([0], domain=Poly.domain + 1))
-    assert_raises(TypeError, p1.__sub__, Poly([0], window=Poly.window + 1))
+    assert_raises(TypeError, op.sub, p1, Poly([0], domain=Poly.domain + 1))
+    assert_raises(TypeError, op.sub, p1, Poly([0], window=Poly.window + 1))
     if Poly is Polynomial:
-        assert_raises(TypeError, p1.__sub__, Chebyshev([0]))
+        assert_raises(TypeError, op.sub, p1, Chebyshev([0]))
     else:
-        assert_raises(TypeError, p1.__sub__, Polynomial([0]))
+        assert_raises(TypeError, op.sub, p1, Polynomial([0]))
 
 
 def check_mul(Poly):
@@ -266,12 +271,12 @@ def check_mul(Poly):
     assert_poly_almost_equal(np.array(c2) * p1, p3)
     assert_poly_almost_equal(p1 * 2, p1 * Poly([2]))
     assert_poly_almost_equal(2 * p1, p1 * Poly([2]))
-    assert_raises(TypeError, p1.__mul__, Poly([0], domain=Poly.domain + 1))
-    assert_raises(TypeError, p1.__mul__, Poly([0], window=Poly.window + 1))
+    assert_raises(TypeError, op.mul, p1, Poly([0], domain=Poly.domain + 1))
+    assert_raises(TypeError, op.mul, p1, Poly([0], window=Poly.window + 1))
     if Poly is Polynomial:
-        assert_raises(TypeError, p1.__mul__, Chebyshev([0]))
+        assert_raises(TypeError, op.mul, p1, Chebyshev([0]))
     else:
-        assert_raises(TypeError, p1.__mul__, Polynomial([0]))
+        assert_raises(TypeError, op.mul, p1, Polynomial([0]))
 
 
 def check_floordiv(Poly):
@@ -293,13 +298,40 @@ def check_floordiv(Poly):
     assert_poly_almost_equal(2 // p2, Poly([0]))
     assert_poly_almost_equal(p2 // 2, 0.5*p2)
     assert_raises(
-        TypeError, p1.__floordiv__, Poly([0], domain=Poly.domain + 1))
+        TypeError, op.floordiv, p1, Poly([0], domain=Poly.domain + 1))
     assert_raises(
-        TypeError, p1.__floordiv__, Poly([0], window=Poly.window + 1))
+        TypeError, op.floordiv, p1, Poly([0], window=Poly.window + 1))
     if Poly is Polynomial:
-        assert_raises(TypeError, p1.__floordiv__, Chebyshev([0]))
+        assert_raises(TypeError, op.floordiv, p1, Chebyshev([0]))
     else:
-        assert_raises(TypeError, p1.__floordiv__, Polynomial([0]))
+        assert_raises(TypeError, op.floordiv, p1, Polynomial([0]))
+
+
+def check_truediv(Poly):
+    # true division is valid only if the denominator is a Number and
+    # not a python bool.
+    p1 = Poly([1,2,3])
+    p2 = p1 * 5
+
+    for stype in np.ScalarType:
+        if not issubclass(stype, Number) or issubclass(stype, bool):
+            continue
+        s = stype(5)
+        assert_poly_almost_equal(op.truediv(p2, s), p1)
+        assert_raises(TypeError, op.truediv, s, p2)
+    for stype in (int, long, float):
+        s = stype(5)
+        assert_poly_almost_equal(op.truediv(p2, s), p1)
+        assert_raises(TypeError, op.truediv, s, p2)
+    for stype in [complex]:
+        s = stype(5, 0)
+        assert_poly_almost_equal(op.truediv(p2, s), p1)
+        assert_raises(TypeError, op.truediv, s, p2)
+    for s in [tuple(), list(), dict(), bool(), np.array([1])]:
+        assert_raises(TypeError, op.truediv, p2, s)
+        assert_raises(TypeError, op.truediv, s, p2)
+    for ptype in classes:
+        assert_raises(TypeError, op.truediv, p2, ptype(1))
 
 
 def check_mod(Poly):
@@ -321,12 +353,12 @@ def check_mod(Poly):
     assert_poly_almost_equal(np.array(c4) % p2, p3)
     assert_poly_almost_equal(2 % p2, Poly([2]))
     assert_poly_almost_equal(p2 % 2, Poly([0]))
-    assert_raises(TypeError, p1.__mod__, Poly([0], domain=Poly.domain + 1))
-    assert_raises(TypeError, p1.__mod__, Poly([0], window=Poly.window + 1))
+    assert_raises(TypeError, op.mod, p1, Poly([0], domain=Poly.domain + 1))
+    assert_raises(TypeError, op.mod, p1, Poly([0], window=Poly.window + 1))
     if Poly is Polynomial:
-        assert_raises(TypeError, p1.__mod__, Chebyshev([0]))
+        assert_raises(TypeError, op.mod, p1, Chebyshev([0]))
     else:
-        assert_raises(TypeError, p1.__mod__, Polynomial([0]))
+        assert_raises(TypeError, op.mod, p1, Polynomial([0]))
 
 
 def check_divmod(Poly):
@@ -464,8 +496,8 @@ def check_pow(Poly):
     for i in range(5):
         assert_poly_almost_equal(tst**i, tgt)
         tgt = tgt * tst
-    assert_raises(ValueError, tgt.__pow__, 1.5)
-    assert_raises(ValueError, tgt.__pow__, -1)
+    assert_raises(ValueError, op.pow, tgt, 1.5)
+    assert_raises(ValueError, op.pow, tgt, -1)
 
 
 def check_call(Poly):