diff --git a/ChangeLog b/ChangeLog index 1f63044ccc..5d5ad5a4ce 100644 --- a/ChangeLog +++ b/ChangeLog @@ -6,6 +6,16 @@ What's New in astroid 2.4.0? ============================ Release Date: TBA +* All the ``numpy ufunc`` functions derived now from a common class that + implements the specific ``reduce``, ``accumulate``, ``reduceat``, + ``outer`` and ``at`` methods. + + Close PyCQA/pylint#2885 + +* The ``shape`` attribute of a ``numpy ndarray`` is now a ``ndarray`` + + Close PyCQA/pylint#3139 + * Don't ignore special methods when inspecting gi classes Close #728 diff --git a/astroid/brain/brain_numpy_core_umath.py b/astroid/brain/brain_numpy_core_umath.py index 459d38c727..9e03bb9e01 100644 --- a/astroid/brain/brain_numpy_core_umath.py +++ b/astroid/brain/brain_numpy_core_umath.py @@ -16,84 +16,120 @@ def numpy_core_umath_transform(): ) return astroid.parse( """ + class FakeUfunc: + def __init__(self): + self.__doc__ = str() + self.__name__ = str() + self.nin = 0 + self.nout = 0 + self.nargs = 0 + self.ntypes = 0 + self.types = None + self.identity = None + self.signature = None + + @classmethod + def reduce(cls, a, axis=None, dtype=None, out=None): + return numpy.ndarray([0, 0]) + + @classmethod + def accumulate(cls, array, axis=None, dtype=None, out=None): + return numpy.ndarray([0, 0]) + + @classmethod + def reduceat(cls, a, indices, axis=None, dtype=None, out=None): + return numpy.ndarray([0, 0]) + + @classmethod + def outer(cls, A, B, **kwargs): + return numpy.ndarray([0, 0]) + + @classmethod + def at(cls, a, indices, b=None): + return numpy.ndarray([0, 0]) + + class FakeUfuncOneArg(FakeUfunc): + def __call__(self, x, {opt_args:s}): + return numpy.ndarray([0, 0]) + + class FakeUfuncOneArgBis(FakeUfunc): + def __call__(self, x, {opt_args:s}): + return numpy.ndarray([0, 0]), numpy.ndarray([0, 0]) + + class FakeUfuncTwoArgs(FakeUfunc): + def __call__(self, x1, x2, {opt_args:s}): + return numpy.ndarray([0, 0]) + # Constants e = 2.718281828459045 euler_gamma = 0.5772156649015329 - # No arg functions - def geterrobj(): return [] - - # One arg functions - def seterrobj(errobj): return None - # One arg functions with optional kwargs - def arccos(x, {opt_args:s}): return numpy.ndarray((0, 0)) - def arccosh(x, {opt_args:s}): return numpy.ndarray((0, 0)) - def arcsin(x, {opt_args:s}): return numpy.ndarray((0, 0)) - def arcsinh(x, {opt_args:s}): return numpy.ndarray((0, 0)) - def arctan(x, {opt_args:s}): return numpy.ndarray((0, 0)) - def arctanh(x, {opt_args:s}): return numpy.ndarray((0, 0)) - def cbrt(x, {opt_args:s}): return numpy.ndarray((0, 0)) - def conj(x, {opt_args:s}): return numpy.ndarray((0, 0)) - def conjugate(x, {opt_args:s}): return numpy.ndarray((0, 0)) - def cosh(x, {opt_args:s}): return numpy.ndarray((0, 0)) - def deg2rad(x, {opt_args:s}): return numpy.ndarray((0, 0)) - def degrees(x, {opt_args:s}): return numpy.ndarray((0, 0)) - def exp2(x, {opt_args:s}): return numpy.ndarray((0, 0)) - def expm1(x, {opt_args:s}): return numpy.ndarray((0, 0)) - def fabs(x, {opt_args:s}): return numpy.ndarray((0, 0)) - def frexp(x, {opt_args:s}): return (numpy.ndarray((0, 0)), numpy.ndarray((0, 0))) - def isfinite(x, {opt_args:s}): return numpy.ndarray((0, 0)) - def isinf(x, {opt_args:s}): return numpy.ndarray((0, 0)) - def log(x, {opt_args:s}): return numpy.ndarray((0, 0)) - def log1p(x, {opt_args:s}): return numpy.ndarray((0, 0)) - def log2(x, {opt_args:s}): return numpy.ndarray((0, 0)) - def logical_not(x, {opt_args:s}): return numpy.ndarray((0, 0)) - def modf(x, {opt_args:s}): return (numpy.ndarray((0, 0)), numpy.ndarray((0, 0))) - def negative(x, {opt_args:s}): return numpy.ndarray((0, 0)) - def rad2deg(x, {opt_args:s}): return numpy.ndarray((0, 0)) - def radians(x, {opt_args:s}): return numpy.ndarray((0, 0)) - def reciprocal(x, {opt_args:s}): return numpy.ndarray((0, 0)) - def rint(x, {opt_args:s}): return numpy.ndarray((0, 0)) - def sign(x, {opt_args:s}): return numpy.ndarray((0, 0)) - def signbit(x, {opt_args:s}): return numpy.ndarray((0, 0)) - def sinh(x, {opt_args:s}): return numpy.ndarray((0, 0)) - def spacing(x, {opt_args:s}): return numpy.ndarray((0, 0)) - def square(x, {opt_args:s}): return numpy.ndarray((0, 0)) - def tan(x, {opt_args:s}): return numpy.ndarray((0, 0)) - def tanh(x, {opt_args:s}): return numpy.ndarray((0, 0)) - def trunc(x, {opt_args:s}): return numpy.ndarray((0, 0)) + arccos = FakeUfuncOneArg() + arccosh = FakeUfuncOneArg() + arcsin = FakeUfuncOneArg() + arcsinh = FakeUfuncOneArg() + arctan = FakeUfuncOneArg() + arctanh = FakeUfuncOneArg() + cbrt = FakeUfuncOneArg() + conj = FakeUfuncOneArg() + conjugate = FakeUfuncOneArg() + cosh = FakeUfuncOneArg() + deg2rad = FakeUfuncOneArg() + exp2 = FakeUfuncOneArg() + expm1 = FakeUfuncOneArg() + fabs = FakeUfuncOneArg() + frexp = FakeUfuncOneArgBis() + isfinite = FakeUfuncOneArg() + isinf = FakeUfuncOneArg() + log = FakeUfuncOneArg() + log1p = FakeUfuncOneArg() + log2 = FakeUfuncOneArg() + logical_not = FakeUfuncOneArg() + modf = FakeUfuncOneArgBis() + negative = FakeUfuncOneArg() + rad2deg = FakeUfuncOneArg() + reciprocal = FakeUfuncOneArg() + rint = FakeUfuncOneArg() + sign = FakeUfuncOneArg() + signbit = FakeUfuncOneArg() + sinh = FakeUfuncOneArg() + spacing = FakeUfuncOneArg() + square = FakeUfuncOneArg() + tan = FakeUfuncOneArg() + tanh = FakeUfuncOneArg() + trunc = FakeUfuncOneArg() # Two args functions with optional kwargs - def bitwise_and(x1, x2, {opt_args:s}): return numpy.ndarray((0, 0)) - def bitwise_or(x1, x2, {opt_args:s}): return numpy.ndarray((0, 0)) - def bitwise_xor(x1, x2, {opt_args:s}): return numpy.ndarray((0, 0)) - def copysign(x1, x2, {opt_args:s}): return numpy.ndarray((0, 0)) - def divide(x1, x2, {opt_args:s}): return numpy.ndarray((0, 0)) - def equal(x1, x2, {opt_args:s}): return numpy.ndarray((0, 0)) - def floor_divide(x1, x2, {opt_args:s}): return numpy.ndarray((0, 0)) - def fmax(x1, x2, {opt_args:s}): return numpy.ndarray((0, 0)) - def fmin(x1, x2, {opt_args:s}): return numpy.ndarray((0, 0)) - def fmod(x1, x2, {opt_args:s}): return numpy.ndarray((0, 0)) - def greater(x1, x2, {opt_args:s}): return numpy.ndarray((0, 0)) - def hypot(x1, x2, {opt_args:s}): return numpy.ndarray((0, 0)) - def ldexp(x1, x2, {opt_args:s}): return numpy.ndarray((0, 0)) - def left_shift(x1, x2, {opt_args:s}): return numpy.ndarray((0, 0)) - def less(x1, x2, {opt_args:s}): return numpy.ndarray((0, 0)) - def logaddexp(x1, x2, {opt_args:s}): return numpy.ndarray((0, 0)) - def logaddexp2(x1, x2, {opt_args:s}): return numpy.ndarray((0, 0)) - def logical_and(x1, x2, {opt_args:s}): return numpy.ndarray([0, 0]) - def logical_or(x1, x2, {opt_args:s}): return numpy.ndarray([0, 0]) - def logical_xor(x1, x2, {opt_args:s}): return numpy.ndarray([0, 0]) - def maximum(x1, x2, {opt_args:s}): return numpy.ndarray((0, 0)) - def minimum(x1, x2, {opt_args:s}): return numpy.ndarray((0, 0)) - def nextafter(x1, x2, {opt_args:s}): return numpy.ndarray((0, 0)) - def not_equal(x1, x2, {opt_args:s}): return numpy.ndarray((0, 0)) - def power(x1, x2, {opt_args:s}): return numpy.ndarray((0, 0)) - def remainder(x1, x2, {opt_args:s}): return numpy.ndarray((0, 0)) - def right_shift(x1, x2, {opt_args:s}): return numpy.ndarray((0, 0)) - def subtract(x1, x2, {opt_args:s}): return numpy.ndarray((0, 0)) - def true_divide(x1, x2, {opt_args:s}): return numpy.ndarray((0, 0)) + bitwise_and = FakeUfuncTwoArgs() + bitwise_or = FakeUfuncTwoArgs() + bitwise_xor = FakeUfuncTwoArgs() + copysign = FakeUfuncTwoArgs() + divide = FakeUfuncTwoArgs() + equal = FakeUfuncTwoArgs() + floor_divide = FakeUfuncTwoArgs() + fmax = FakeUfuncTwoArgs() + fmin = FakeUfuncTwoArgs() + fmod = FakeUfuncTwoArgs() + greater = FakeUfuncTwoArgs() + hypot = FakeUfuncTwoArgs() + ldexp = FakeUfuncTwoArgs() + left_shift = FakeUfuncTwoArgs() + less = FakeUfuncTwoArgs() + logaddexp = FakeUfuncTwoArgs() + logaddexp2 = FakeUfuncTwoArgs() + logical_and = FakeUfuncTwoArgs() + logical_or = FakeUfuncTwoArgs() + logical_xor = FakeUfuncTwoArgs() + maximum = FakeUfuncTwoArgs() + minimum = FakeUfuncTwoArgs() + nextafter = FakeUfuncTwoArgs() + not_equal = FakeUfuncTwoArgs() + power = FakeUfuncTwoArgs() + remainder = FakeUfuncTwoArgs() + right_shift = FakeUfuncTwoArgs() + subtract = FakeUfuncTwoArgs() + true_divide = FakeUfuncTwoArgs() """.format( opt_args=ufunc_optional_keyword_arguments ) diff --git a/astroid/brain/brain_numpy_ndarray.py b/astroid/brain/brain_numpy_ndarray.py index 8c231a3009..302ee4a482 100644 --- a/astroid/brain/brain_numpy_ndarray.py +++ b/astroid/brain/brain_numpy_ndarray.py @@ -29,7 +29,7 @@ def __init__(self, shape, dtype=float, buffer=None, offset=0, self.nbytes = None self.ndim = None self.real = None - self.shape = None + self.shape = numpy.ndarray([0, 0]) self.size = None self.strides = None diff --git a/tests/unittest_brain_numpy_core_umath.py b/tests/unittest_brain_numpy_core_umath.py index 7030323fc5..51b696f611 100644 --- a/tests/unittest_brain_numpy_core_umath.py +++ b/tests/unittest_brain_numpy_core_umath.py @@ -13,7 +13,8 @@ HAS_NUMPY = False from astroid import builder -from astroid import nodes +from astroid import nodes, bases +from astroid import util @unittest.skipUnless(HAS_NUMPY, "This test requires the numpy library.") @@ -22,10 +23,6 @@ class NumpyBrainCoreUmathTest(unittest.TestCase): Test of all members of numpy.core.umath module """ - no_arg_ufunc = ("geterrobj",) - - one_arg_ufunc_spec = ("seterrobj",) - one_arg_ufunc = ( "arccos", "arccosh", @@ -38,7 +35,6 @@ class NumpyBrainCoreUmathTest(unittest.TestCase): "conjugate", "cosh", "deg2rad", - "degrees", "exp2", "expm1", "fabs", @@ -52,7 +48,6 @@ class NumpyBrainCoreUmathTest(unittest.TestCase): "modf", "negative", "rad2deg", - "radians", "reciprocal", "rint", "sign", @@ -96,7 +91,7 @@ class NumpyBrainCoreUmathTest(unittest.TestCase): "true_divide", ) - all_ufunc = no_arg_ufunc + one_arg_ufunc_spec + one_arg_ufunc + two_args_ufunc + all_ufunc = one_arg_ufunc + two_args_ufunc constants = ("e", "euler_gamma") @@ -137,42 +132,35 @@ def test_numpy_core_umath_functions(self): for func in self.all_ufunc: with self.subTest(func=func): inferred = self._inferred_numpy_attribute(func) - self.assertIsInstance(inferred, nodes.FunctionDef) - - def test_numpy_core_umath_functions_no_arg(self): - """ - Test that functions with no arguments have really no arguments. - """ - for func in self.no_arg_ufunc: - with self.subTest(func=func): - inferred = self._inferred_numpy_attribute(func) - self.assertFalse(inferred.argnames()) - - def test_numpy_core_umath_functions_one_arg_spec(self): - """ - Test the arguments names of functions. - """ - exact_arg_names = ["errobj"] - for func in self.one_arg_ufunc_spec: - with self.subTest(func=func): - inferred = self._inferred_numpy_attribute(func) - self.assertEqual(inferred.argnames(), exact_arg_names) + self.assertIsInstance(inferred, bases.Instance) def test_numpy_core_umath_functions_one_arg(self): """ Test the arguments names of functions. """ - exact_arg_names = ["x", "out", "where", "casting", "order", "dtype", "subok"] + exact_arg_names = [ + "self", + "x", + "out", + "where", + "casting", + "order", + "dtype", + "subok", + ] for func in self.one_arg_ufunc: with self.subTest(func=func): inferred = self._inferred_numpy_attribute(func) - self.assertEqual(inferred.argnames(), exact_arg_names) + self.assertEqual( + inferred.getattr("__call__")[0].argnames(), exact_arg_names + ) def test_numpy_core_umath_functions_two_args(self): """ Test the arguments names of functions. """ exact_arg_names = [ + "self", "x1", "x2", "out", @@ -185,7 +173,9 @@ def test_numpy_core_umath_functions_two_args(self): for func in self.two_args_ufunc: with self.subTest(func=func): inferred = self._inferred_numpy_attribute(func) - self.assertEqual(inferred.argnames(), exact_arg_names) + self.assertEqual( + inferred.getattr("__call__")[0].argnames(), exact_arg_names + ) def test_numpy_core_umath_functions_kwargs_default_values(self): """ @@ -196,7 +186,8 @@ def test_numpy_core_umath_functions_kwargs_default_values(self): with self.subTest(func=func): inferred = self._inferred_numpy_attribute(func) default_args_values = [ - default.value for default in inferred.args.defaults + default.value + for default in inferred.getattr("__call__")[0].args.defaults ] self.assertEqual(default_args_values, exact_kwargs_default_values) @@ -217,24 +208,64 @@ def test_numpy_core_umath_functions_return_type(self): Test that functions which should return a ndarray do return it """ ndarray_returning_func = [ - f - for f in self.all_ufunc - if f not in ("geterrobj", "seterrobj", "frexp", "modf") + f for f in self.all_ufunc if f not in ("frexp", "modf") ] - licit_array_types = (".ndarray",) + for func_ in ndarray_returning_func: + with self.subTest(typ=func_): + inferred_values = list(self._inferred_numpy_func_call(func_)) + self.assertTrue( + len(inferred_values) == 1 + or len(inferred_values) == 2 + and inferred_values[-1].pytype() is util.Uninferable, + msg="Too much inferred values ({}) for {:s}".format( + inferred_values[-1].pytype(), func_ + ), + ) + self.assertTrue( + inferred_values[0].pytype() == ".ndarray", + msg="Illicit type for {:s} ({})".format( + func_, inferred_values[-1].pytype() + ), + ) + + def test_numpy_core_umath_functions_return_type_tuple(self): + """ + Test that functions which should return a pair of ndarray do return it + """ + ndarray_returning_func = ("frexp", "modf") + for func_ in ndarray_returning_func: with self.subTest(typ=func_): inferred_values = list(self._inferred_numpy_func_call(func_)) self.assertTrue( len(inferred_values) == 1, - msg="Too much inferred value for {:s}".format(func_), + msg="Too much inferred values ({}) for {:s}".format( + inferred_values, func_ + ), ) self.assertTrue( - inferred_values[-1].pytype() in licit_array_types, + inferred_values[-1].pytype() == "builtins.tuple", msg="Illicit type for {:s} ({})".format( func_, inferred_values[-1].pytype() ), ) + self.assertTrue( + len(inferred_values[0].elts) == 2, + msg="{} should return a pair of values. That's not the case.".format( + func_ + ), + ) + for array in inferred_values[-1].elts: + effective_infer = [m.pytype() for m in array.inferred()] + self.assertTrue( + ".ndarray" in effective_infer, + msg=( + "Each item in the return of {} " + "should be inferred as a ndarray and not as {}".format( + func_, effective_infer + ) + ), + ) if __name__ == "__main__": diff --git a/tox.ini b/tox.ini index 4a7cb488f7..077e836bf8 100644 --- a/tox.ini +++ b/tox.ini @@ -18,8 +18,8 @@ deps = ; we have a brain for nose ; we use pytest for tests nose - py35,py36: numpy - py35,py36: attr + py35,py36,py37: numpy + py35,py36,py37: attr py35,py36,py37: typed_ast>=1.4.0,<1.5 pytest python-dateutil