numpyのndarrayをitertools.productに渡すのは微妙だからnumpy.nditerを使うほうがいい。それかnumpy.meshgrid。

itertools.product

import numpy
import itertools

x1 = np.arange(4)
x2 = np.arange(3)

for p, q in itertools.product(x1, x2):
  print(p, q)

これを実行すると

0 0
0 1
0 2
1 0
1 1
1 2
2 0
2 1
2 2
3 0
3 1
3 2

問題:itertoolsのインポートがうるさい。numpyでできないのか?

numpy.nditer

broadcasting in numpy.nditer

import numpy as np

x1 = np.arange(4)
x2 = np.arange(3)

for p, q in np.nditer([x1, x2.reshape(-1, 1)]):
  print(p, q)

np.nditerに指定したndarrayにはブロードキャストが効く。

0 0
1 0
2 0
3 0
0 1
1 1
2 1
3 1
0 2
1 2
2 2
3 2

ブロードキャストに慣れていれば使いやすいはず。

numpy.meshgrid

ループがしたいのではなく最初から結果を積んだ配列が欲しい場合。

import numpy as np

x1 = np.linspace(-1, 1, 10)
x2 = np.linspace(-2, 2, 10)

X1, X2 = np.meshgrid(x1, x2)

X = np.c_[X1.ravel(), X2.ravel()] #shape = (100, 2)
# or
X = np.r_[X1.ravel(), X2.ravel()] #shape = (2, 100)
# or
X = np.array(np.meshgrid(x1, x2)).reshape(2, -1).T #shape = (100, 2)
# or
X = np.array(np.meshgrid(x1, x2)).reshape(2, -1) #shape = (2, 100)

どちら向きに積むかわかりやすいのは前半のnp.c_np.r_で明示的に積んでいる方。

後者はunpackの代わりに強引にnp.arrayを使うことやreshapeの2が引数の2個と対応していないといけない、あげく形によってはTをつける必要あり(reshape(-1, 2)は不可)。短くは書けるのでこれが気にならなければ可。