There might by expressions returning a real result (not just None) and having a side-effect at the same time.
I have doubts that:
- if a result is impossible to get, should we insist that the side-effect of
list.extend
should be precisely defined/documented?
- if yes, will it be defined/documented also for
set.update
and all other possible uses (in stdlib or in general) ?
the list being changed in list.extend() is not a side effect, it is the intended result of the method call.
I would note that this is why “Functional programming languages generally emphasize immutability”.
But Python does not. Note that tuples don’t have an extend()
method.
There might by expressions returning a real result (not just None)
And this is why the mutating methods on the Python built-in objects all return None
.
Yes, you can bury that mutating method call inside a more complex expression (or a function for that matter) that does return something, and that may have a return value, and could hide that “side effect”. I would argue that that’s also why functions with side effects are often discouraged.
To use this example:
def bad_idea(some_data):
a_global_list.extend(some_data)
if you passed a iterator that raised before completion, then the function would raise, but a_global_list
would be altered. And that could be a mess.
But the answer is not that extend()
should be atomic, but that you shouldn’t write functions with side effects like that.
NOTE: I’m a bit curious if this would be different if Python was re-designed now:
Early Python used to be “all about Sequences” slicing, extend() all sorts of things were kind of designed around sequences.
Iterators were introduced later – and plugging iterators (in particular lazy iterators) into the Sequence-focused language does create a few places of impedance mismatch.
maybe extend()
would have been implemented differently if iterators were the initial use case.
But anyway, as you point out, it’s all of the mutating methods in all classes that take iterators that this issue could effect.