Why don't we have a simpler way of declaring arrays and 2D arrays?

Instead of doing stuff like for looping and multiplying lists of [0]s, can’t we just do something like new list[2][2]? Is it a legacy issue or is there some other reason?

Everyone I know would “just use numpy”. Python technically does have Array in the stlib, but I don’t know anyone who uses that.

It’s probably a legacy issue. But since numpy is really good in its niche, there is no practical problem. And it’s quite understandable the core team doesn’t want to have even more code to support.

It’s also not too bad if you use list comprehensions: [[0 for _ in range (columns)] for _ in range(rows)]. Creating a new language construct is a little overkill when we already have an idiomatic solution.

What should new list[2][2] be? I guess a list of two lists of two elements each? But what are the elements?

If this is something you do frequently, you can create a helper function for it, so you can use newlist(2, 2) to construct one. This also allows you to be in control of things like what value fills that list (should it be 0, None, 0.0, or something else?), which would otherwise be impossible for the language to decide.

If you wanted to have it typed, you could accept a generic and then use that to initialize the values.

Something like:

>>> newlist[int](2, 2)
[[0,0],[0,0]]
>>> newlist[str](2, 2)
[["",""],["",""]]
>>> newlist[float](2, 2)
[[0.0,0.0],[0.0,0.0]]

That would require some hacky runtime type hint inspection, but it’s a nice interface and lets you kill 2 birds with one stone.

Could also just accept a factory argument too:

>>> newlist(2, 2, factory=int)
[[0,0],[0,0]]
>>> newlist(2, 2, factory=str)
[["",""],["",""]]
>>> newlist(2, 2, factory=float)
[[0.0,0.0],[0.0,0.0]]

Which is definitely more extensible since you can pass it any factory function.

You can even mix the factory with generics, while avoiding inspection!

from collections.abc import Callable

def newlist[T](rows: int, columns: int, factory: Callable[[], T]) -> list[list[T]]:
    return [[factory() for _ in range(columns)] for _ in range(rows)]

reveal_type(newlist(2, 2, int)) # list[list[int]]
reveal_type(newlist(2, 2, str)) # list[list[str]]
reveal_type(newlist(2, 2, lambda: object())) # list[list[object]]

This is definitely the simplest and easiest way that doesn’t require you to inspect the hints or do anything else.

It’s also only a few lines of code and can be reused everywhere, so I think the point still stands that you don’t really need a builtin for something like this. Especially if it’s for a specific purpose since this style gives you way more flexibility than anything builtin!

I’d say it is more of a foundational issue. If you are creating a list of lists to act as a two dimensional list then you need to know about referencing the same list multiple times vs referencing individual copies.