Shorter alternative for 'lambda' keyword?
As a person who never uses lambdas in his code except for debugging purposes, I can suggest several alternatives.
I won't speak about defining your own syntax in an editor (you can't define operators in a pure Python though: Python: defining my own operators?) but just about built-in stuff.
- Methods of built-in types:
Compare the following:
Usingwords = ['cat', 'dog', 'shark'] result_1 = map(lambda x: x.upper(), words) result_2 = (x.upper() for x in words) result_3 = map(str.upper, words) # ['CAT', 'DOG', 'SHARK']
map
withstr.upper
is shorter than bothmap
withlambda
and a generator expression proposed in another answer.
You can find lots of other methods in the docs for different types such asint
,float
,str
,bytes
and others, which you can use in the same manner. For example, checking if numbers are integers:numbers = [1.0, 1.5, 2.0, 2.5] result_1 = map(lambda x: x.is_integer(), numbers) result_2 = (x.is_integer() for x in numbers) result_3 = map(float.is_integer, numbers) # [True, False, True, False]
Class methods:
In a similar way you can usemap
with class methods:class Circle: def __init__(self, radius): self.radius = radius def area(self): return 3.14 * self.radius ** 2 circles = [Circle(2), Circle(10)] result_1 = map(lambda x: x.area(), circles) result_2 = (x.area() for x in circles) result_3 = map(Circle.area, circles) # [12.56, 314.0]
operator
module:itemgetter
:
This one is used when you want to select elements by their indices:from operator import itemgetter numbers = [[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 0, 1]] result_1 = map(lambda x: x[0], numbers) result_2 = (x[0] for x in numbers) result_3 = map(itemgetter(0), numbers) # [0, 4, 8]
While it is longer than generator expression in the given example, it will actually be shorter when you want to select several elements at once:
result_1 = map(lambda x: (x[0], x[2], x[3]), numbers) result_2 = ((x[0], x[2], x[3]) for x in numbers) result_3 = map(itemgetter(0, 2, 3), numbers) # [(0, 2, 3), (4, 6, 7), (8, 0, 1)]
You can also use
itemgetter
with dictionaries:data = [{'time': 0, 'temperature': 290, 'pressure': 1.01}, {'time': 10, 'temperature': 295, 'pressure': 1.04}, {'time': 20, 'temperature': 300, 'pressure': 1.07}] result_1 = map(lambda x: (x['time'], x['pressure']), data) result_2 = ((x['time'], x['pressure']) for x in data) result_3 = map(itemgetter('time', 'pressure'), data) # [(0, 1.01), (10, 1.04), (20, 1.07)]
attrgetter
This one is used to get attributes of objects:from collections import namedtuple from operator import attrgetter Person = namedtuple('Person', ['name', 'surname', 'age', 'car']) people = [Person(name='John', surname='Smith', age=40, car='Tesla'), Person(name='Mike', surname='Smith', age=50, car=None)] result_1 = map(lambda x: (x.name, x.age, x.car), people) result_2 = ((x.name, x.age, x.car) for x in people) result_3 = map(attrgetter('name', 'age', 'car'), people) # [('John', 40, 'Tesla'), ('Mike', 50, None)]
It is longer than the generator expression version, so I'm leaving it here just for completeness. Of course, you can import
attrgetter
asget
and it will be shorter but nobody really does that. Usingattrgetter
has an advantage, though, that you could take it out as a separate callable that could be used more than once (same aslambda
):get_features = attrgetter('name', 'age', 'car') group_1_features = map(get_features, people) group_2_features = map(get_features, other_people) ...
Another alternative worth to mention is using
fget
method of properties:result = map(Person.age.fget, people)
I've never seen anyone using it though, so prepare to give explanation to people who will read your code if you use it.
contains
:
Used to check if an element is present in another object/container:from functools import partial from operator import contains fruits = {'apple', 'peach', 'orange'} objects = ['apple', 'table', 'orange'] result_1 = map(lambda x: x in fruits, objects) result_2 = (x in fruits for x in objects) is_fruit = partial(contains, fruits) result_3 = map(is_fruit, objects) # [True, False, True]
This, though, has a drawback of creating an additional
partial
object. Another way to write this would be to use__contains__
method:result = map(fruits.__contains__, objects)
But some people argue that it is a bad practice to use dunder methods as those are just for a private use.
Mathematical operations:
For example, if you would want to sum pairs of numbers, you could useoperator.add
:from itertools import starmap from operator import add pairs = [(1, 2), (4, 3), (1, 10), (2, 5)] result_1 = map(lambda x: x[0] + x[1], pairs) result_2 = (x + y for x, y in pairs) result_3 = starmap(add, pairs) # [3, 7, 11, 7]
If you are fine with two additional imports then this is the shortest option. Note that we use
itertools.starmap
here because we need to unpack tuples of numbers before supplying them toadd(a, b)
function.
I think I covered most of the cases that I constantly encounter that could be rewritten without lambda
. If you know more, please, write it in a comment, and I will add it to my answer.
The good news is: You don't need to use map
or filter
at all, you can use generator expressions (lazy) or list comprehensions (eager) instead and thus avoid lambda
s completely.
So instead of:
lines = map(lambda x: x.strip(), sys.stdin)
Just use:
# You can use either of those in Python 2 and 3, but map has changed between
# Python 2 and Python 3 so I'll present both equivalents:
lines = (x.strip() for x in sys.stdin) # generator expression (Python 3 map equivalent)
lines = [x.strip() for x in sys.stdin] # list comprehension (Python 2 map equivalent)
It's probably also faster if you use comprehensions. Very few functions are actually faster when used in map
or filter
- and using a lambda
there is more of an anti-pattern (and slow).
The question only contained an example for map
, but you can also replace filter
. For example if you want to filter
out odd numbers:
filter(lambda x: x%2==0, whatever)
You can use a conditional comprehension instead:
(x for x in whatever if x%2==0)
[x for x in whatever if x%2==0]
You could even combine a map
and filter
in one comprehension:
(x*2 for x in whatever if x%2==0)
Just consider how that would look like with map
and filter
:
map(lambda x: x*2, filter(lambda x: x%2==0, whatever))
Note: That doesn't mean lambda
isn't useful! There are lots of places where lambda
s are very handy. Consider the key
argument for sorted
(and likewise for min
and max
) or functools.reduce
(but better keep away from that function, most of the times a normal for
-loop is more readable) or itertools
that require a predicate function: itertools.accumulate
, itertools.dropwhile
, itertools.groupby
and itertools.takewhile
. Just to name a few examples where a lambda
could be useful, there are probably lots of other places as well.