Why do dict keys support list subtraction but not tuple subtract

ghz 1months ago ⋅ 6 views

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.