Repository: numpy/numpy

Issue:
full works on lists but with very bad performance
When you pass a python list to full, it actually works as expected but with bad performance. The documentation says that only a scalar is accepted, but because it does the right thing, it's very easy for people to end up with code like this that's correct but inefficient.

```
import numpy
x = numpy.full(100, range(100))
```

The reason it's inefficient is because the source looks like this:
```
    if dtype is None:
        dtype = array(fill_value).dtype
    a = empty(shape, dtype, order)
    multiarray.copyto(a, fill_value, casting='unsafe')
    return a

```
If the dtype is not specified, the branch actually creates a copy of the whole array, throws it away, and then does it again.

I would suggest either explicitly supporting this use case, and improving performance and docs to match. Or, try to do some kind of basic check to reject this input as unacceptable.


Discussion:
Perhaps simply:
```
    if dtype is None:
        fill_value = asarray(fill_value)
        dtype = fill_value.dtype
    a = empty(shape, dtype, order)
    multiarray.copyto(a, fill_value, casting='unsafe')
    return a
```
I tested out the solution proposed by @eric-wieser, for small shapes it is about 2x faster, for larger arrays it converges to the same time as the current implementation.

Could be a dumb question, but why do arrays need to be supported here? To me this feels already supported via `np.tile`. Would it make more sense to only support scalars here? We would need to update the documentation too since it states "scalar or array_like"
copyto does an `np.asarray` call internally anyway, so that seems like the right thing to do. @dojafrat do you want to open a PR for it?
Yeah for sure. Just to make sure we are 100% on the same page before I get going. My plan is to:
1) Enforce only scalar values are passed
2) Add an error message for non-scalar values
3) Update documentation

Lmk if that sounds and I will get right to it
Sorry, while I personally agree with full being only used for scalars, I suspect that may be a bit of a harder sell, and would require a deprecation period at the very least. I was first thinking of just doing the optimization here.
>  I suspect that may be a bit of a harder sell, and would require a deprecation period at the very least. 

I don't think so. `fill_value` is clearly documented as scalar, allowing anything else is accidental and can be removed without any deprecation. A clear error message here would be nice. +1 for @dojafrat's plan.
I suspect that people are relying on this behavior, so I'd be -1 on deprecating it. It wouldn't surprise me at all if someone doing image manipulation does something like:
```python
orange = np.array([255, 128, 0], dtype=np.uint8)
my_image = np.full((480, 640, 3), orange, dtype=np.uint8)
``` 
I think that we rely on `np.full` working for arrays internally in `np.select`, with calls like:
```python
>>> x = np.arange(3)[:,None,None]
>>> np.select([x == 0, x[:,None,None] == 1], [np.zeros((2, 2)), np.eye(2)], np.ones((2, 2)))
array([[[0., 0.],
        [0., 0.]],

       [[1., 0.],
        [0., 1.]],

       [[1., 1.],
        [1., 1.]]])
```
Here, the array is initialized with `np.full((3, 2, 2), np.ones((2, 2)))`
@dojafrat: are you still working on this?
@jjerphan there is no open PR and it the discussion has been a while ago. I think it is up for grabs from anyone interested.
Hey, sorry I thought I added a commit, does that not show up as a pull request? 
Hi @dojafrat, you need to create a pull request.
@dojafrat: you can do this [here](https://github.com/numpy/numpy/compare/master...dojafrat:performant-full)
Ah thank you! Just pulled the trigger on that. Apologies for the hold up, I'm new to this workflow and thought I was done after simply doing a commit.

Resolving patch:
diff --git a/numpy/core/numeric.py b/numpy/core/numeric.py
--- a/numpy/core/numeric.py
+++ b/numpy/core/numeric.py
@@ -310,7 +310,8 @@ def full(shape, fill_value, dtype=None, order='C'):
 
     """
     if dtype is None:
-        dtype = array(fill_value).dtype
+        fill_value = asarray(fill_value)
+        dtype = fill_value.dtype
     a = empty(shape, dtype, order)
     multiarray.copyto(a, fill_value, casting='unsafe')
     return a


Test patch:
diff --git a/numpy/core/tests/test_api.py b/numpy/core/tests/test_api.py
--- a/numpy/core/tests/test_api.py
+++ b/numpy/core/tests/test_api.py
@@ -542,3 +542,10 @@ def test_broadcast_arrays():
     result = np.broadcast_arrays(a, b)
     assert_equal(result[0], np.array([(1, 2, 3), (1, 2, 3), (1, 2, 3)], dtype='u4,u4,u4'))
     assert_equal(result[1], np.array([(1, 2, 3), (4, 5, 6), (7, 8, 9)], dtype='u4,u4,u4'))
+
+@pytest.mark.parametrize(["shape", "fill_value", "expected_output"],
+        [((2, 2), [5.0,  6.0], np.array([[5.0, 6.0], [5.0, 6.0]])),
+         ((3, 2), [1.0,  2.0], np.array([[1.0, 2.0], [1.0, 2.0], [1.0,  2.0]]))])
+def test_full_from_list(shape, fill_value, expected_output):
+    output = np.full(shape, fill_value)
+    assert_equal(output, expected_output)