Why do `key in dict` and `key in dict.keys()` have the same output?

To understand why key in dct returns the same result as key in dct.keys() one needs to look in the past. Historically in Python 2, one would test the existence of a key in dictionary dct with dct.has_key(key). This was changed for Python 2.2, when the preferred way became key in dct, which basically did the same thing:

In a minor related change, the in operator now works on dictionaries, so key in dict is now equivalent to dict.has_key(key)

The behaviour of in is implemented internally in terms of the __contains__ dunder method. Its behaviour is documented in the Python language reference - 3 Data Model:

object.__contains__(self, item)

Called to implement membership test operators. Should return true if item is in self, false otherwise. For mapping objects, this should consider the keys of the mapping rather than the values or the key-item pairs. For objects that don’t define __contains__(), the membership test first tries iteration via __iter__(), then the old sequence iteration protocol via __getitem__(), see this section in the language reference.

(emphasis mine; dictionaries in Python are mapping objects)

In Python 3, the has_key method was removed altogether and now there the correct way to test for the existence of a key is solely key in dict, as documented.


In contrast with the 2 above, key in dct.keys() has never been the correct way of testing whether a key exists in a dictionary. The result of both your examples is indeed the same, however key in dct.keys() is slightly slower on Python 3 and is abysmally slow on Python 2.

key in dct returns true, if the key is found as a key in the dct in almost constant time operation - it does not matter whether there are two or a million keys - its time complexity is constant on average case (O(1))

dct.keys() in Python 2 creates a list of all keys; and in Python 3 a view of keys; both of these objects understand the key in x. With Python 2 it works like for any iterable; the values are iterated over and True is returned as soon as one value is equal to the given value (here key).

In practice, in Python 2 you'd find key in dct.keys() much slower than key in dict (key in dct.keys() scales linearly with the number of keys - its time complexity is O(n) - both dct.keys(), which builds a list of all keys, and key in key_list are O(n))

In Python 3, the key in dct.keys() won't be much slower than key in dctas the view does not make a list of the keys, and the access still would be O(1), however in practice it would be slower by at least a constant value, and it is 7 more characters, so there is usually practically no reason to use it, even if on Python 3.


Python data model dictates that generally a membership test are normally implemented as an iteration through a sequence unless a container object supplies the special method __contains__ .

As mentioned further in the document, for objects that does not implement the __contains__ special method, the membership test first tries iteration via __iter__(), then the old sequence iteration protocol via __getitem__().

Its important to know that for dictionaries, dict.keys() returns either an iterator either a dictionary view (Python 3.X) or a sequence (more precisely a list), in Python (2.X). Membership test for a sequence/list is an O(n) complexity where as for a dictionary like object which is implemented as a hash map, or a dictionary view which supports operation like supports operations like membership test and iteration has a complexity of O(1).

So for Python 2.X, there is a distinct difference in terms of what both does, that might impact performance, where as for Python 2.X, the only overhead is an extra function call.

In any case, it is always preferred to use the membership on the dict object rather than using the membership test on a dictionary view or a sequence which is returned by dict.keys