If your function really takes two parameters you probably want to map not 2d to 3d, but rather 2xMxN to 3xMxN. For this change your first line to something like
gridx, gridy = np.meshgrid(np.linspace(0, 1, 50), np.linspace(0, 1, 50))
or even use the more economical ix_ which has the advantage of not swapping axes
gridy, gridx = np.ix_(np.linspace(0, 1, 50), np.linspace(0, 1, 50))
If your function f does not handle array arguments then as @Jacques Gaudin points out np.vectorize is probably what you want. Be warned that vectorize is primarily a convenience function it doesn't make things faster. It does useful things like broadcasting which is why using ix_ actually works
f_wrapped = np.vectorize(f)
result = f_wrapped(gridy, gridx)
Note that result in your case is a 3-tuple of 50 x 50 arrays, i.e. is grouped by output. This is convenient if you want to chain vectorized functions. If you want all in one big array just convert result to array and optionally use transpose to rearrange axes, e.g.
triplets_last = np.array(result).transpose((1, 2, 0))
(x, y)values looks like on anN x Ngrid... Is this to imply that you have a 2D array of tuples?