Testing Key-Value Membership In Dictionaries

Hello Python Ideas, Currently, to check whether a single key is in a dictionary, we use the "in" keyword. However, there is no built-in support for checking if a key-value pair belongs in a dictionary. Currently, we presuppose that the object being checked has the same type as that of the key. What if we allowed the "in" operator to accept a tuple that denotes a (mapped) key-value pair? Let us consider how that might work using the canonical example given in the tutorial:
tel = {'jack': 4098, 'sape': 4139}
As you can see, the "in" operator would interpret the object as either a key or a key-value pair depending on the actual types of the object, key and value. In the key itself happens to be a tuple, then the key-value membership test would involve a nested tuple, whose first item is a tuple denoting the key. Best Regards, Karthick Sankarachary

You can do the same check using a default argument in dict.get such as...
denotes a (mapped) key-value pair?
Let us consider how that might work using the canonical example given in
- John On Sun, Oct 9, 2011 at 4:25 PM, Karthick Sankarachary < karthick.sankarachary@gmail.com> wrote: that the

Karthick Sankarachary schrieb am So, 09. Okt 2011, um 13:25:16 -0700:
These semantics are inconsistent:
d = {"a": 1, ("a", 2): 3} ("a", 2) in d
What result do you expect now? False, because the value for "a" is 1, or True, because ("a", 2) occurs as a key? Neither answer seems to be useful. Moreover, the functionality you want is easily available as something like tel.get("jack") == "4098" Cheers, Sven

Hi On 2011-10-09 22:25, Karthick Sankarachary wrote:
Yes there is. You use the "viewitems()" method in 2.7 or the "items()" method in 3.x to get a set-like view on the dictionary, then test membership of that. So for your example:
The above code *works* in all versions of python (AFAIK), but for large dictionaries it can be quite inefficient in all versions before 3.0.
From 3.0 forward this is the "one obvious way to do it", and is fast no matter the size of the dictionary (expected O(1) time).
HTH - Jacob

Python is developed according to practical need rather than theoretical consistency or symmetry. In Python 3, 'key in dict' is the same as 'key in dict.keys()'. Similarly, 'for item in dict' is the same as 'for item in dict.keys()'. One can look for or iterate over key-value items by replacing 'keys' with 'items' in either construction above. Dict keys get the special treatment of being the default because they are the most commonly used for search and iteration. -- Terry Jan Reedy

Karthick Sankarachary wrote:
Of course there is. (key, value) in d.items() is explicit and simple. Or if you prefer to avoid an O(N) search through a list, the following is only a tiny bit less convenient, but it has the advantage of being effectively O(1): key in d and d[key] == value Fast, efficient, simple, obvious, explicit and avoids "magic" behaviour.
Currently, we presuppose that the object being checked has the same type as that of the key.
No we don't. d = {1: 2} "spam" in d works perfectly. There's no need to presuppose that the key being tested for has the same type as the actual keys in the dict. The only thing we suppose is that the given key is hashable. Other than the assumption of hashability, there are no assumptions made about the keys. You want to change that, by assuming that keys aren't tuples.
What if we allowed the "in" operator to accept a tuple that denotes a (mapped) key-value pair?
Having a single function (or in this case, operator) perform actions with different semantics depending on the value of the argument is rarely a good idea, especially not for something as basic and fundamental as containment tests. Let me give you an analogy: suppose somebody proposed that `substring in string` should do something different depending on whether substring was made up of digits or not: "x" in "a12-50" => returns False, simple substring test "2" in "a12-50" => returns False, numeric test 2 in the range 12-50? "20" in "a12-50" => returns True, because 20 is in the range 12-50 And then they propose a hack to avoid the magic test and fall back on the standard substring test: "^20" in "a12-50" => returns False, simple substring test "^20" in "a12034" => returns True and a way to escape the magic hack: "^^20" in "a12034" => returns False "^^20" in "a1^2034" => returns True When you understand why this is a terrible idea, you will understand why overloading the in operator to magically decide whether you want a key test or a key/value test is also a terrible idea. The fact that in your proposal, you are checking the class of the argument, but in mine I am checking the value of the argument, is irrelevant. One of the problems with the proposal is that in the event that the argument is not a literal, you can't tell what the code will do: x in some_dict With your proposal, you simply can't tell: it might test for a key, or it might test for a key/value test, and you can't tell which until runtime when x has it's value. But normally you want one or the other: the two tests have different meanings: you either want to test for a key, or for a key/value. I can't think of any case where you don't care which you get. So to write a function that includes a test for a key, you are forced to write complicated code with an inconvenient type-check: def function(d, key): # I only want to test for a key if ((key,) in d if isinstance(key, tuple) else key in d): print("key detected") To say nothing of the code that you will break -- this is a major backwards incompatible change, changing the semantics of existing code. Even if it were a good idea, it would be a bad idea. -- Steven

You can do the same check using a default argument in dict.get such as...
denotes a (mapped) key-value pair?
Let us consider how that might work using the canonical example given in
- John On Sun, Oct 9, 2011 at 4:25 PM, Karthick Sankarachary < karthick.sankarachary@gmail.com> wrote: that the

Karthick Sankarachary schrieb am So, 09. Okt 2011, um 13:25:16 -0700:
These semantics are inconsistent:
d = {"a": 1, ("a", 2): 3} ("a", 2) in d
What result do you expect now? False, because the value for "a" is 1, or True, because ("a", 2) occurs as a key? Neither answer seems to be useful. Moreover, the functionality you want is easily available as something like tel.get("jack") == "4098" Cheers, Sven

Hi On 2011-10-09 22:25, Karthick Sankarachary wrote:
Yes there is. You use the "viewitems()" method in 2.7 or the "items()" method in 3.x to get a set-like view on the dictionary, then test membership of that. So for your example:
The above code *works* in all versions of python (AFAIK), but for large dictionaries it can be quite inefficient in all versions before 3.0.
From 3.0 forward this is the "one obvious way to do it", and is fast no matter the size of the dictionary (expected O(1) time).
HTH - Jacob

Python is developed according to practical need rather than theoretical consistency or symmetry. In Python 3, 'key in dict' is the same as 'key in dict.keys()'. Similarly, 'for item in dict' is the same as 'for item in dict.keys()'. One can look for or iterate over key-value items by replacing 'keys' with 'items' in either construction above. Dict keys get the special treatment of being the default because they are the most commonly used for search and iteration. -- Terry Jan Reedy

Karthick Sankarachary wrote:
Of course there is. (key, value) in d.items() is explicit and simple. Or if you prefer to avoid an O(N) search through a list, the following is only a tiny bit less convenient, but it has the advantage of being effectively O(1): key in d and d[key] == value Fast, efficient, simple, obvious, explicit and avoids "magic" behaviour.
Currently, we presuppose that the object being checked has the same type as that of the key.
No we don't. d = {1: 2} "spam" in d works perfectly. There's no need to presuppose that the key being tested for has the same type as the actual keys in the dict. The only thing we suppose is that the given key is hashable. Other than the assumption of hashability, there are no assumptions made about the keys. You want to change that, by assuming that keys aren't tuples.
What if we allowed the "in" operator to accept a tuple that denotes a (mapped) key-value pair?
Having a single function (or in this case, operator) perform actions with different semantics depending on the value of the argument is rarely a good idea, especially not for something as basic and fundamental as containment tests. Let me give you an analogy: suppose somebody proposed that `substring in string` should do something different depending on whether substring was made up of digits or not: "x" in "a12-50" => returns False, simple substring test "2" in "a12-50" => returns False, numeric test 2 in the range 12-50? "20" in "a12-50" => returns True, because 20 is in the range 12-50 And then they propose a hack to avoid the magic test and fall back on the standard substring test: "^20" in "a12-50" => returns False, simple substring test "^20" in "a12034" => returns True and a way to escape the magic hack: "^^20" in "a12034" => returns False "^^20" in "a1^2034" => returns True When you understand why this is a terrible idea, you will understand why overloading the in operator to magically decide whether you want a key test or a key/value test is also a terrible idea. The fact that in your proposal, you are checking the class of the argument, but in mine I am checking the value of the argument, is irrelevant. One of the problems with the proposal is that in the event that the argument is not a literal, you can't tell what the code will do: x in some_dict With your proposal, you simply can't tell: it might test for a key, or it might test for a key/value test, and you can't tell which until runtime when x has it's value. But normally you want one or the other: the two tests have different meanings: you either want to test for a key, or for a key/value. I can't think of any case where you don't care which you get. So to write a function that includes a test for a key, you are forced to write complicated code with an inconvenient type-check: def function(d, key): # I only want to test for a key if ((key,) in d if isinstance(key, tuple) else key in d): print("key detected") To say nothing of the code that you will break -- this is a major backwards incompatible change, changing the semantics of existing code. Even if it were a good idea, it would be a bad idea. -- Steven
participants (6)
-
Jacob Holm
-
John O'Connor
-
Karthick Sankarachary
-
Steven D'Aprano
-
Sven Marnach
-
Terry Reedy