You could at least use apply_along_axis
:
result = np.apply_along_axis(lambda point: np.outer(point, point), 1, p)
Surprisingly, however, this is in fact slower than your method:
In [ ]: %%timeit N = int(1e4); p = np.random.random((N, 3))
...: result = np.apply_along_axis(lambda point: np.outer(point, point), 1, p)
61.5 ms ± 1.84 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [ ]: %%timeit N = int(1e4); p = np.random.random((N, 3))
...: result = np.zeros((N, 3, 3))
...: for i in range(N):
...: result[i, :, :] = np.outer(p[i, :], p[i, :])
46 ms ± 709 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)