I'm answering this in the context of passing a literal iterable to a constructor or function beyond which the type does not matter. If you need to pass in a hashable argument, you need a tuple. If you'll need it mutated, pass in a list (so that you don't add tuples to tuples thereby multiplying the creation of objects.)
The answer to your question is that the better option varies situationally. Here's the tradeoffs.
Starting with list
type, which is mutable, it preallocates memory for future extension:
a = np.zeros([2, 3])
Pro: It's easily readable.
Con: It wastes memory, and it's less performant.
Next, the tuple
type, which is immutable. It doesn't need to preallocate memory for future extension, because it can't be extended.
b = np.zeros((2, 3))
Pro: It uses minimal memory, and it's more performant.
Con: It's a little less readable.
My preference is to pass tuple literals where memory is a consideration, for example, long-running scripts that will be used by lots of people. On the other hand, when I'm using an interactive interpreter, I prefer to pass lists because they're a bit more readable, the contrast between the square brackets and the parenthesis makes for easy visual parsing.
You should only care about performance in a function, where the code is compiled to bytecode:
>>> min(timeit.repeat('foo()', 'def foo(): return (0, 1)'))
0.080030765042010898
>>> min(timeit.repeat('foo()', 'def foo(): return [0, 1]'))
0.17389221549683498
Finally, note that the performance consideration will be dwarfed by other considerations. You use Python for speed of development, not for speed of algorithmic implementation. If you use a bad algorithm, your performance will be much worse. It's also very performant in many respects as well. I consider this only important insomuch as it may scale, if it can ameliorate heavily used processes from dying a death of a thousand cuts.