What you're doing is adding a new axis: the "column"-axis. b didn't have that before, so now it's a column vector and would be added column wise; it will essentially act as if it was repeated in the columns, and a repeated in the rows:
a+b[:, None] = [1,2] + [[3], = [[1,2], + [[3],[3],
[4]] [1,2]] [4],[4]]
And here is how/why:
First things first: numpy does elementwise addition and multiplication by default. That means if a=np.array([1,2]) then a+2=np.array([1+2,2+2])=np.array([3,5]).
import numpy as np
A = np.array([[1, 2],
[3, 4]])
B = np.array([[1,1],
[0,0]])
Here A+B would be element wise and we would get
A+B = [[1+1,2+1], = [[2,3],
[3+0,4+0]] = [3,4]]
If we transpose the matrix B (using B.T)
now B.T would have the value
B.T= np.array([[1,0],
[1,0]])
And if we do it elementwise this time we'd get:
A+B.T=[[1, 2] + [[1,0] = [[2,2],
[3, 4]] [1,0]] [4,4]]
Another thing to notice is that (B not transposed)
a = np.array([1,2])
B+a = [[1,2], + [[2, 3],
[1,2]] [1, 2]]
And it's also elementwise, but on both rows! That is a was practically "repeated" to have two rows, and added elementwise to B.
Further, Numpy docs says that None, in slicing, is a different way of writing np.newaxis. In your case, what the None option in slicing does is basically transposing the b-vector before addition!
The exact same result could have been obtained by
import numpy as np
a = np.array([1, 2])
b = np.array([3, 4])
c=a+b.reshape(2,1)
d=a+b.reshape(-1,1)
e=a+b[:, np.newaxis]
Here c, d and e have the same values!
b[:, None]returns that array with shape(2,1), or why adding a(2,)shape array and a(2, 1)shape array produce that resultb.shapeandb[:, None].shape.