String S is a substring of string T if and only if there exists an index i such that T[i:i+len(S)] == S When S is an empty string, you have T[i:i] = '' = S , so all the results are correct.
Also note that T.index('') returns 0, because index returns the first index in which the substring appears, and T[0:0] = '' so that the result is definitely correct.
Thus, an empty string is a substring of each string, and all these results are a direct consequence of this.
Also note that this is characteristic of strings, because strings are sequences of characters that themselves are strings of length one. For other types of sequences (e.g. list or tuple s) you will not get the same results:
>>> (1,2,3).index(()) Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: tuple.index(x): x not in tuple >>> [1,2,3].index([1,2]) Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: [1, 2] is not in list >>> [] in [1,2,3] False
This is because list and tuple only check members, not under list or sub << 210> s, because their elements can be of any type. Imagine the case of ((1,2),1,2).index((1,2)) . Should index check "substrings" (and therefore return 1) for members (and therefore return 0) or make some ugly mix (for example, check subcategories first and then for members)? In python, it was decided to search for members only, as this is simpler, and usually this is what you want. Checking for individual tuples would lead to really odd results in the general case, and performing "mixtures" would often lead to unpredictable results.