Florian Brucker
Florian Brucker

Reputation: 10375

Tuple unpacking while indexing

This works:

x = ['foo', 'bar']
y = [*x]
print(y)  # prints ['foo', 'bar']

but this doesn't:

x = ['foo', 'bar']
y[*x]  # raises SyntaxError (not NameError!)

How can I unpack tuples while indexing?

Here are two examples where I'd like to use this approach, but I'm more interested in understanding why *-unpacking seems not to be supported in indexing in general.

import numpy as np

def lookup(a: np.ndarray, coordinates: tuple) -> float:
    return a[*coordinates]

a1 = np.zeros((2, 2))
print(lookup(a1, (0, 1))  # Should print 0

a2 = np.zeros(2, 2, 2))
print(lookup(a2, (0, 0, 1))  # Should print 0

or

from typing import Tuple

NUM_DIMENSIONS = 2  # Might change at a later point in time

# Should be equivalent to Tuple[float ,float]
Result = Tuple[*([float] * NUM_DIMENSIONS)]

def get() -> Result:
    ...

Upvotes: 2

Views: 1374

Answers (3)

Simplecode
Simplecode

Reputation: 589

x = ['foo', 'bar']
y[*x]  # raises SyntaxError (not NameError!)

This is raising syntax error because the * and x are being taken as two separate things, rather than one. For eg : if I do the following

type(*x)

this returns the error

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: type() takes 1 or 3 arguments

which means that *x is not treated as a single entity but two separate.

Also,

>>> x = ['foo', 'bar']
>>> print(*x)
foo bar
>>> print(x)
['foo', 'bar']
>>> y[*x]
  File "<stdin>", line 1
    y[*x]
      ^
SyntaxError: invalid syntax
>>> y[foo bar]  #this is y[*x]
  File "<stdin>", line 1
    y[foo bar]
            ^
SyntaxError: invalid syntax

Upvotes: -1

Florian Brucker
Florian Brucker

Reputation: 10375

Python's indexing already has built-in support for tuples in general (not just for NumPy), so there is no need for unpacking here.

In general, foo[x] is syntactic sugar for type(foo).__getitem__(foo, x). Let's see how that works in detail:

class Foo: 
    def __getitem__(self, key): 
        print(repr(key)) 

foo = Foo()

If we index into foo with a single value then it is passed to __getitem__ unchanged, no matter whether it is a scalar, a list, or a tuple:

foo[0]       # prints 0
foo[(0, 1)]  # prints (0, 1)
foo[[0, 1]]  # prints [0, 1]

The interesting case is what happens when we supply multiple values directly while indexing (without wrapping them in a tuple or list):

foo[0, 1]  # prints (0, 1)

So multiple values are automatically wrapped in a tuple! foo cannot distinguish between foo[0, 1] and foo[(0, 1)]. This is because in the Python grammar, the index is an expression (or a slice, but that doesn't apply here) -- and in an expression, a , forms a tuple:

x = 1, 2
print(repr(x))  # prints (1, 2)

Hence, argument parsing in indexing works differently then for function calls (where commas separate arguments instead of forming tuples).

So, overall, there is no need for iterator unpacking in indexing. Simply convert your iterator to a tuple use it as the index.

Upvotes: 2

Seb
Seb

Reputation: 4585

Referring to your example:

import numpy as np

def lookup(a: np.ndarray, coordinates: tuple) -> float:
    return a[*coordinates]

a1 = np.zeros((2, 2))
print(lookup(a1, (0, 1))

a2 = np.zeros(2, 2, 2))
print(lookup(a2, (0, 0, 1))

NumPy already accepts indexing like a[coordinates] where coordinates is a tuple, without need for the star operator:

>>> a = np.arange(8).reshape(2, 2, 2)
>>> a[(1, 1, 0)]
6

And if you index with lists, you get a different kind of useful behaviour:

>> a[[1, 1, 0], [0]]
array([[4, 5],
       [4, 5],
       [0, 1]])

Upvotes: 0

Related Questions