Two functions that differ only in one line, and the version that replaces both. Read what gets passed in the third call.
def double_all(numbers):
return [n * 2 for n in numbers]
def square_all(numbers):
return [n ** 2 for n in numbers]
def map_all(numbers, transform):
return [transform(n) for n in numbers]
print(map_all([1, 2, 3], double)) # the function itself - no brackets
- No brackets means the function, not its result
double refers to the function object; double(3) calls it. Passing the first is what lets the receiving code decide when — and how many times — to call it. Adding brackets by accident is the commonest mistake here, and it usually shows up as 'NoneType is not callable'.
- One parameter replaces a family of functions
double_all and square_all differ by a single expression, so they are the same function with a hole in it. Naming that hole and passing it in is the entire idea behind map, sorted’s key, and every callback you will ever write.
- A dict comprehension keeps the keys
{key: transform(value) for key, value in mapping.items()} builds a new dictionary and never touches the original. The keys pass through untouched because you wrote them untouched — which is the difference between this and a transformation that also renames things.
You need every key with a changed value. Which comprehension builds a dictionary, and what does .items() give you to build it from?
mapping.items() yields key/value pairs. A dict comprehension has the form {key_expression: value_expression for ... in ...} — the key expression stays as it is and the value expression calls transform.
return {key: transform(value) for key, value in mapping.items()}. One line, no loop variable to manage, no mutation of the input, and nothing to do about a transform that raises — it travels on its own.
def apply_to_values(mapping, transform): return {____: ____(value) for key, value in mapping.____()}
def apply_to_values(mapping, transform):
return {key: transform(value) for key, value in mapping.items()}
There is no try/except, and that is the right call. If the caller’s transform raises, that is their bug in their code, and hiding it would leave them with a dictionary quietly missing entries. A general-purpose helper should almost never catch exceptions from a function it was handed — it cannot know what they mean.
for key in mapping: mapping[key] = transform(mapping[key]) is shorter and modifies the caller’s dictionary. It also changes what happens if transform raises half way through — you would be left with a mapping that is partly transformed, which is worse than either outcome.
map(transform, values) does the same job for a sequence and returns a lazy iterator. There is no built-in for the dictionary case because the comprehension is already this short — which is a good rule for when to write a helper at all: when the comprehension would be repeated, not when it would merely exist.
If the transformation depends on the key as well as the value, this signature is wrong and you want transform(key, value) — do not smuggle the key in through a closure. If you also need to change or drop keys, write the comprehension inline where it is used; a helper that transforms everything is only worth naming while it stays that simple. And for large numeric mappings, pandas or a plain array will beat a per-key Python call by a wide margin.
- Write apply_to_keys as well, then decide whether a single function with a `which` parameter would be better or worse than two clear ones.
- Add a filter so entries whose transformed value is None are dropped, and notice that you now have two responsibilities in one function.
- Make it recursive so nested dictionaries are transformed all the way down — and work out what should happen to a value that is a list of dictionaries.