Skip to content
Closed
5 changes: 5 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ What's New in astroid 2.4.0?
============================
Release Date: TBA

* Added a call to ``register_transform`` for all functions of the ``brain_numpy_core_multiarray``
module in case the current node is an instance of ``astroid.Name``

Close #666

* Added some functions of the ``numpy.core.multiarray`` module

Close PyCQA/pylint#3208
Expand Down
38 changes: 25 additions & 13 deletions astroid/brain/brain_numpy_core_fromnumeric.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,30 @@

"""Astroid hooks for numpy.core.fromnumeric module."""

import functools
import astroid


def numpy_core_fromnumeric_transform():
return astroid.parse(
"""
def sum(a, axis=None, dtype=None, out=None, keepdims=None, initial=None):
return numpy.ndarray([0, 0])
"""
)


astroid.register_module_extender(
astroid.MANAGER, "numpy.core.fromnumeric", numpy_core_fromnumeric_transform
from brain_numpy_utils import infer_numpy_member

# None is present inside the inferred values of the call to sum function because
#  the variable out in the sum function is not correctly inferred.
# This module is here to help astroid to determine the exact type of this variable and
# thus to infer correctly the result of the call to sum function (i.e without None in the inferred values).
def looks_like_out_name_in_numpy_sum(
member_name: str, node: astroid.node_classes.NodeNG
) -> bool:
if (
isinstance(node, astroid.Name)
and node.name == "out"
and node.frame().name == "sum"
and node.frame().parent.name.startswith("numpy")
):
return True
return False


inference_function = functools.partial(infer_numpy_member, "out = np.ndarray([0, 0])")
astroid.MANAGER.register_transform(
astroid.Name,
astroid.inference_tip(inference_function),
functools.partial(looks_like_out_name_in_numpy_sum, "out"),
)
3 changes: 3 additions & 0 deletions astroid/brain/brain_numpy_core_function_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
from brain_numpy_utils import looks_like_numpy_member, infer_numpy_member


# TODO: In fact there is obviously a less intrusive way to correctly infer those functions.
# They are all defined in a .py file thus there is no need to redefine them here.
# See what is done in brain_numpy_core_fromnumeric module
METHODS_TO_BE_INFERRED = {
"linspace": """def linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis=0):
return numpy.ndarray([0, 0])""",
Expand Down
5 changes: 5 additions & 0 deletions astroid/brain/brain_numpy_core_multiarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,8 @@ def vdot(a, b):
astroid.inference_tip(inference_function),
functools.partial(looks_like_numpy_member, method_name),
)
astroid.MANAGER.register_transform(
astroid.Name,
astroid.inference_tip(inference_function),
functools.partial(looks_like_numpy_member, method_name),
)
2 changes: 2 additions & 0 deletions astroid/brain/brain_numpy_core_umath.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ def __call__(self, x1, x2, {opt_args:s}):
trunc = FakeUfuncOneArg()

# Two args functions with optional kwargs
add = FakeUfuncTwoArgs()
bitwise_and = FakeUfuncTwoArgs()
bitwise_or = FakeUfuncTwoArgs()
bitwise_xor = FakeUfuncTwoArgs()
Expand All @@ -123,6 +124,7 @@ def __call__(self, x1, x2, {opt_args:s}):
logical_xor = FakeUfuncTwoArgs()
maximum = FakeUfuncTwoArgs()
minimum = FakeUfuncTwoArgs()
multiply = FakeUfuncTwoArgs()
nextafter = FakeUfuncTwoArgs()
not_equal = FakeUfuncTwoArgs()
power = FakeUfuncTwoArgs()
Expand Down
12 changes: 10 additions & 2 deletions astroid/brain/brain_numpy_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,17 @@ def looks_like_numpy_member(
:param node: node to test
:return: True if the node is a member of numpy
"""
return (
if (
isinstance(node, astroid.Attribute)
and node.attrname == member_name
and isinstance(node.expr, astroid.Name)
and _is_a_numpy_module(node.expr)
)
):
return True
if (
isinstance(node, astroid.Name)
and node.name == member_name
and node.root().name.startswith("numpy")
):
return True
return False
20 changes: 11 additions & 9 deletions tests/unittest_brain_numpy_core_fromnumeric.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
except ImportError:
HAS_NUMPY = False

from astroid import builder
from astroid import builder, Uninferable


@unittest.skipUnless(HAS_NUMPY, "This test requires the numpy library.")
Expand All @@ -39,20 +39,22 @@ def test_numpy_function_calls_inferred_as_ndarray(self):
"""
Test that calls to numpy functions are inferred as numpy.ndarray
"""
licit_array_types = (".ndarray",)
licit_array_types = set([".ndarray", Uninferable])
for func_ in self.numpy_functions:
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_[0]),
inferred_values = set(
[v.pytype() for v in self._inferred_numpy_func_call(*func_)]
)
self.assertTrue(
inferred_values[-1].pytype() in licit_array_types,
msg="Illicit type for {:s} ({})".format(
func_[0], inferred_values[-1].pytype()
len(inferred_values) == 2,
msg="Too much inferred values {} for {:s}".format(
inferred_values, func_[1]
),
)
self.assertTrue(
inferred_values == licit_array_types,
msg="Illicit type for {:s} ({})".format(func_[0], inferred_values),
)


if __name__ == "__main__":
Expand Down
4 changes: 3 additions & 1 deletion tests/unittest_brain_numpy_core_function_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ def test_numpy_function_calls_inferred_as_ndarray(self):
inferred_values = list(self._inferred_numpy_func_call(*func_))
self.assertTrue(
len(inferred_values) == 1,
msg="Too much inferred value for {:s}".format(func_[0]),
msg="Too much inferred values {} for {:s}".format(
inferred_values, func_[0]
),
)
self.assertTrue(
inferred_values[-1].pytype() in licit_array_types,
Expand Down
2 changes: 2 additions & 0 deletions tests/unittest_brain_numpy_core_umath.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class NumpyBrainCoreUmathTest(unittest.TestCase):
)

two_args_ufunc = (
"add",
"bitwise_and",
"bitwise_or",
"bitwise_xor",
Expand All @@ -82,6 +83,7 @@ class NumpyBrainCoreUmathTest(unittest.TestCase):
"logical_xor",
"maximum",
"minimum",
"multiply",
"nextafter",
"not_equal",
"power",
Expand Down
7 changes: 4 additions & 3 deletions tests/unittest_regrtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import unittest
import textwrap

from astroid import MANAGER, Instance, nodes
from astroid import MANAGER, Instance, nodes, util
from astroid.bases import BUILTINS
from astroid.builder import AstroidBuilder, extract_node
from astroid import exceptions
Expand Down Expand Up @@ -92,12 +92,13 @@ def test_numpy_crash(self):
data = """
from numpy import multiply

multiply(1, 2, 3)
multiply([1, 2], [3, 4])
"""
astroid = builder.string_build(data, __name__, __file__)
callfunc = astroid.body[1].value.func
inferred = callfunc.inferred()
self.assertEqual(len(inferred), 1)
self.assertEqual(len(inferred), 2)
self.assertTrue(inferred[-1].pytype() is util.Uninferable)

@require_version("3.0")
def test_nameconstant(self):
Expand Down