With a large set of operators and ufunc, numpy can easily do this kind of element-wise computation, using a fundamental concept of broadcasting:
In [155]: A = np.array(['one','two','three']); B = np.array(['four','two'])
In [156]: A[:,None] == B # compare a (3,1) array with a (2,)
Out[156]:
array([[False, False],
[False, True],
[False, False]])
But this works much better with numeric arrays. There aren't many actions that work with string arrays.
A few of the np.char functions work with 2 arrays:
In [159]: np.char.join(B,A[:,None])
Out[159]:
array([['ofournfoure', 'otwontwoe'],
['tfourwfouro', 'ttwowtwoo'],
['tfourhfourrfourefoure', 'ttwohtwortwoetwoe']], dtype='<U21')
Expanding the arrays into 2d arrays (functionally the same as A[:,None]):
In [160]: np.meshgrid(A,B,indexing='ij')
Out[160]:
[array([['one', 'one'],
['two', 'two'],
['three', 'three']], dtype='<U5'),
array([['four', 'two'],
['four', 'two'],
['four', 'two']], dtype='<U4')]
np.vectorize can be used to apply broadcasting to a function that takes scalar inputs (single strings). For small arrays it tends to be slower than list comprehension, but for large arrays it scales somewhat better.
In short, there's a lot of power in numpy for doing numeric element-wise operations, less so for strings.