Two shapes over one block of memory, and the two things that follow from that.
import numpy as np values = np.array([1, 2, 3, 4, 5, 6]) values.reshape(2, 3) # -> [[1, 2, 3], [4, 5, 6]] fills row by row values.reshape(2, 3).base is values # -> True, it is a VIEW values.reshape(-1, 3) # -> the same thing: -1 means 'work it out' # 6 values, 3 per row, so 2 rows. Only one dimension may be -1. values.reshape(4, 2) # ValueError: cannot reshape array of size 6 into shape (4,2) values.reshape(2, 3).copy() # an independent array, if you need one
- The data does not move, so the shape must fit exactly
Reshaping only changes the strides numpy walks the buffer with, so it is free at any size — and it means the new shape has to account for every value. Six numbers cannot become four rows of two, and numpy says so rather than padding or truncating. Compare chunking a list, where a short final piece is normal.
- -1 means 'derive this one from the size'
reshape(-1, 3) says three per row and however many rows that takes. It is not a wildcard: exactly one dimension may be -1, and the size must still divide evenly. Using it is better than computing len(values) // width yourself, because numpy does the division and the check together.
- Reshape returns a view, so writing into it reaches the original
The result shares the input’s memory whenever it can — which is the same trap as slicing, arriving by a different route. A function that hands back a reshaped array has handed back a window onto its argument, and the caller has no way to tell. If independence is part of what you promise, copy.
One reshape, one copy, and two guards in front. Let numpy work out the number of rows rather than computing it yourself.
values.reshape(-1, width) derives the row count and refuses a size that does not divide. Guard the width first, because a width of 0 or −1 would get past reshape with a confusing message or a wrong answer.
reshape returns a view, so .copy() is what makes the result independent — and the contract promises that. numpy’s own ValueError for a size that does not divide is clearer than anything you would write, so let it through rather than pre-checking the division.
import numpy as np def to_rows(values, width): if width < ____: raise ____(f’width must be at least 1, got {width}') return values.reshape(____, width).____()
import numpy as np
def to_rows(values, width):
# Guarded first: 0 would make reshape complain about division, and -1
# has a special meaning it would silently accept.
if width < 1:
raise ValueError(f"width must be at least 1, got {width}")
# -1 derives the row count and refuses a size that does not divide;
# copy(), because reshape alone returns a view onto the caller's data.
return values.reshape(-1, width).copy()
reshape already refuses a width of 0 and of −1 — the first with a division error, the second with "can only specify one unknown dimension", because −1 already means something to it. So the guard is not preventing a wrong answer; it is replacing an error about numpy’s internals with one that names the argument the caller passed. That is a real improvement and a smaller claim than it looks.
reshape(-1, width) computes the row count and validates the fit in one step, with an error message that names the size and the shape. Computing len(values) // width and checking the remainder yourself is three lines that can disagree with what reshape then does — and numpy’s message is better than yours would be.
reshape is free and copy is O(n), so this function is as expensive as the data is large. That is the right trade when independence is promised, and the wrong one when the caller was about to read the result and throw it away. If you find yourself reshaping a large array in a loop, returning the view and documenting it is a legitimate choice — just not a silent one.
If you do not need independence, return the view: reshape is one of numpy’s few genuinely free operations and copying throws that away. If the size does not divide evenly and you want the ragged last row anyway, reshape is the wrong tool entirely — that is chunking, and np.array_split does it. And note the difference between reshape and resize: reshape returns something new and leaves the original alone, while ndarray.resize changes the array in place and will happily pad with zeros, which is almost never what you meant.
- Return the view instead of the copy, name the function so the caller knows, and check that the independence case now fails.
- Add an order argument so the values can fill column by column, and see what that does to the six-value case.
- Compare against np.array_split for a size that does not divide, and decide which behaviour each of your callers actually wants.