Why do dict keys support list subtraction but not tuple subtraction?
Presumably dict_keys are supposed to behave as a set-like object, but they are lacking the difference
method and the subtraction behaviour seems to diverge.
>>> d = {0: 'zero', 1: 'one', 2: 'two', 3: 'three'}
>>> d.keys() - [0, 2]
{1, 3}
>>> d.keys() - (0, 2)
TypeError: 'int' object is not iterable
Why does dict_keys class try to iterate an integer here? Doesn't that violate duck-typing?
>>> dict.fromkeys(['0', '1', '01']).keys() - ('01',)
{'01'}
>>> dict.fromkeys(['0', '1', '01']).keys() - ['01',]
{'1', '0'}
Answer
The dict_keys
view in Python does indeed aim to be a set-like object. However, its behavior when it comes to operations like -
(difference) can be somewhat unintuitive due to specific implementation details, especially when working with iterable inputs like tuples or lists.
Here’s a breakdown of what’s happening in each case you provided:
1. Why dict_keys
treats a single integer differently
When you perform a difference operation with dict_keys
and a list, it works as expected:
d.keys() - [0, 2] # Output: {1, 3}
Here, dict_keys.__sub__
expects an iterable on the right side to use as the collection of elements to remove. In this case, dict_keys
converts the list [0, 2]
into a set of items to subtract.
However, if you try this with a tuple containing integers:
d.keys() - (0, 2) # Raises TypeError
A TypeError
is raised, stating that an 'int' object is not iterable
. This error occurs because dict_keys.__sub__
isn’t interpreting dict_keys - (0, 2)
as the difference between two sets but rather as dict_keys - 0
, with 2
being ignored. This results from dict_keys
treating tuples differently, interpreting them as if they were intended to represent a single item rather than a collection.
To achieve consistency with a list-like difference, you could explicitly cast the tuple to a set or list:
d.keys() - set((0, 2)) # Output: {1, 3}
2. Differences in behavior between tuple
and list
In your second example, we observe similar behavior with strings:
dict.fromkeys(['0', '1', '01']).keys() - ('01',) # Output: {'01'}
When subtracting a single-element tuple ('01',)
, dict_keys
doesn't treat this as an iterable of items to remove but rather as a single entity (in this case, the string '01'
).
In contrast:
dict.fromkeys(['0', '1', '01']).keys() - ['01',] # Output: {'1', '0'}
This operation works as expected because lists are always treated as collections of items to subtract, ensuring consistency with a set-like
interpretation.
Explanation of Duck Typing and dict_keys
The behavior you're observing is due to how dict_keys
views interpret operands based on their types rather than their contents (i.e., based on whether the right operand is a tuple or a list/set). This behavior does not strictly adhere to duck-typing, as dict_keys
interprets tuple
differently from other iterable types.
This inconsistency may not be ideal, but it results from dict_keys
prioritizing compatibility with specific use cases. To ensure predictable results with dict_keys
, always pass sets or lists as operands in difference operations.