Use np.repeat. For example:
v = np.random.normal(size=(4, 1))
np.repeat(v, 3, axis=1)
Output:
array([[ 1.7676415 , 1.7676415 , 1.7676415 ],
[ 0.77139662, 0.77139662, 0.77139662],
[ 1.34501879, 1.34501879, 1.34501879],
[-1.3641335 , -1.3641335 , -1.3641335 ]])
UPDATE: I recommend you use the answer from @balezz (https://stackoverflow.com/a/65795639/5763165) due to speed improvements. If the number of repeats is large the broadcasting method is better:
import timeit
setup = "import numpy as np; v = np.random.normal(size=(1000, 1))"
repeat, multiply = [], []
for i in range(50):
multiply.append(timeit.timeit(f'v * np.ones((v.shape[0], {i}))', setup=setup, number=10000))
repeat.append(timeit.timeit(f'np.repeat(v, {i}, axis=1)', setup=setup, number=10000))
Gives the following:

The improvement of multiply over repeat persists in most cases when varying the size of the input vector as well:
import timeit
repeat, multiply = [], []
for i in [2, 5, 10, 50, 100, 1000, 10000]:
setup = f"import numpy as np; v = np.random.normal(size=({i}, 1))"
repeat.append(timeit.timeit(f'np.repeat(v, 50, axis=1)', setup=setup, number=10000))
multiply.append(timeit.timeit(f'v * np.ones((v.shape[0], 50))', setup=setup, number=10000))
