32

Background:

Python is about simplicity and readable code. It has gotten better over the versions and I am a huge fan! However, typing l a m b d a every time I have to define a lambda is not fun (you may disagree). The problem is, these 6 characters l a m b d a make my statements longer, especially if I nest a couple of lambdas inside maps and filters. I am not nesting more than 2 or three, because it takes away the readability of python, even then typing l a m b d a feels too verbose.

The actual question (is in comments):

# How to rename/alias a keyword to a nicer one? 
lines = map(lmd x: x.strip(), sys.stdin)

# OR, better yet, how to define my own operator like -> in python?
lines = map(x -> x.strip(), sys.stdin)
# Or may be :: operator is pythonic
lines = map(x :: x.strip(), sys.stdin)

# INSTEAD of this ugly one. Taking out this is my goal!
lines = map(lambda x: x.strip(), sys.stdin)

I am happy to add import like this:

from myfuture import lmd_as_lambda
# OR
from myfuture import lambda_operator
Thamme Gowda
  • 11,249
  • 5
  • 50
  • 57
  • 4
    *“when I nest a couple of lambdas inside maps and filters”* Use generator expressions instead? – Ry- Aug 19 '17 at 00:49
  • 2
    Write functions instead of lambdas. They are easier to debug. Lambdas are useful, but ought to be used in moderation. If typing lambda is too troublesome, perhaps that is a sign that you are using them too much. – Bryan Oakley Aug 19 '17 at 00:53
  • 3
    What's wrong with passing `str.strip`? – Ignacio Vazquez-Abrams Aug 19 '17 at 01:21
  • @IgnacioVazquez-Abrams It's not equivalent to `lambda x: x.strip()`. There could be `bytes` (python-3.x) or `unicode`s (python-2.x) in there - or something else entirely that can be stripped. It's unlikely (impossible?) for `sys.stdin` but it could be problematic in more general cases. – MSeifert Aug 19 '17 at 01:23
  • 1
    Other than by avoiding its use, there's no way for you to abbreviate a Python keyword. Unlike C/C++, there's no preprocessor step that expands macros. – martineau Aug 19 '17 at 04:10
  • re: __What's wrong with passing str.strip__ : Thanks but `lambda x: x.strip()` used here is a placeholder or demo example and this question is for all lambdas in general and not a sepcific lambda. Are we going to find alternatives like `str.strip` for all the custom lambdas? I dont think so! – Thamme Gowda Oct 29 '20 at 00:50
  • I know it’s an old question, but in the pursuit of a language with terse syntax—especially for lambdas—I found Scala (where you can do things like `val doubled = ints.map(_ * 2)`) and a package to do the same for Julia: https://juliapackages.com/p/lambdafn. Not sure any of these innovations will ever be a part of Python, but one can dream. – Kevin Li May 08 '22 at 14:32

3 Answers3

25

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 lambdas 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 lambdas 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.

Thamme Gowda
  • 11,249
  • 5
  • 50
  • 57
MSeifert
  • 145,886
  • 38
  • 333
  • 352
  • 1
    Thanks. Today I learned [Generator Expressions](https://www.python.org/dev/peps/pep-0289/), it solves my need. I would accept this answer in a short while. waiting to see if somebody has thoughts about `::` or `->` operators) – Thamme Gowda Aug 19 '17 at 01:52
  • Python 2 supports [generator expressions](https://docs.python.org/2/reference/expressions.html?#generator-expressions), too, so what you said about them and Python 3 is incorrect. – martineau Aug 19 '17 at 04:03
  • @martineau I never said python-2.x doesn't support generators expressions. All I said (and meant) was that the equivalent for `map` is a list-comprehension in python-2.x and a generator expression in python-3.x – MSeifert Aug 19 '17 at 04:34
  • You seem to be saying that a generator expression would not also be equivalent in Python 2. – martineau Aug 19 '17 at 04:45
  • 1
    @martineau It's not equivalent to `map` on python-2.x. But that doesn't mean there are no generator expressions in python-2.x. I've updated the answer and clarified it a bit. Please let me know if it's still ambiguous. – MSeifert Aug 19 '17 at 04:53
  • Doesn't answer the question I came here for (short hand for lambda), but I've learned about generator expressions. So I'm at peace today. +1 – Leo Ufimtsev Jul 15 '19 at 02:00
  • doesn't answer the question - i already use generators. I really want answer to the question itself: how to shorten the `lambda` which is distracting when used over and again – WestCoastProjects Mar 10 '20 at 20:03
  • @javadba the answer, implicitly, is that there is no way. And there are no future plans to add a way. Indeed, Guido almost removed lambda entirely from Python 3 – juanpa.arrivillaga May 13 '20 at 08:43
  • Yes - I do not miss Guido and the BDFL. The popularity of python is surprising given its hostility to functional programming. TIt seems to be coming from a mantra of "one way to do things" and that one way is the quite limited `for comprehensions`. https://stackoverflow.com/questions/49001986/left-to-right-application-of-operations-on-a-list-in-python3 – WestCoastProjects May 13 '20 at 10:58
8

To answer your specific question, the operator module provides several functions intended to replace specific uses of a lambda expression. Here, you can use the methodcaller function to create a function that calls a given method on an object.

from operator import methodcaller as mc

lines = map(mc('strip'), sys.stdin)

However, list comprehensions tend to be preferred to many, if not most, uses of map.

lines = [x.strip() for x in sys.stdin]
chepner
  • 497,756
  • 71
  • 530
  • 681
  • 3
    Yes, the `operator` module is very handy for `map` etc. In this case one could also consider using `str.strip` directly (if there are only pure `str`s). – MSeifert Aug 19 '17 at 01:21
  • 1
    Good point. `methodcaller` is useful when you need to rely on duck typing (i.e., I don't know what type `x` will have, but I know it needs to have a `strip` method) or when you need to pass additional arguments to the method. Otherwise, you can use an unbound method as the function argument. – chepner Aug 19 '17 at 01:31
  • Thanks for the suggestion. 1. Method caller may not be always possible, for instance, if my lambda takes a tuple and swap positions and returns a new tuple. 2. `[x.strip() for x in sys.stdin]` this is elegant but my data streams are lazily `yield`ed from generator. I just learned from @MSeifert answer that python 3 supports lazy evaluations when I swap `[]` with `()` – Thamme Gowda Aug 19 '17 at 01:42
  • 2
    @ThammeGowda Just to clarify: Both python-2.x and 3.x support generator expressions (lazy evaluation). What I meant was that `map` and `filter` are eager in python-2.x but lazy in python-3.x. – MSeifert Aug 19 '17 at 01:43
  • 1
    @MSeifert Thanks. Yes, Just looked it up, it was accepted in 2.4 as PEP-289 https://www.python.org/dev/peps/pep-0289/ – Thamme Gowda Aug 19 '17 at 01:46
  • Python 2 has `itertools.imap`, which is the equivalent of Python 3's `map` for working with arbitrary iterables (including generator expressions). For your tuple-swapping function, try `operator.itemgetter`: `map(itemgetter(1,0), [(1, 'a'), (2,'b')])`. That said, there are cases where you can't avoid writing a user-defined function, but the reason to avoid them where possible is that there is significant overhead to calling a user-defined function; the function returned by `itemgetter(1,0)` should be faster than `lambda (x,y): (y, x)`. – chepner Aug 19 '17 at 02:24
  • This is actually a partial answer to the original question. What about general `lambda` that may not be a simple method invocation -and thus not fit precisely into a `methodcaller`? – WestCoastProjects Mar 10 '20 at 20:06
  • In general, the only reasonable option is to replace the lambda expression with the name of a previously defined function. – chepner Mar 10 '20 at 20:12
7

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.

  1. Methods of built-in types:
    Compare the following:
    words = ['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']
    
    Using map with str.upper is shorter than both map with lambda and a generator expression proposed in another answer.
    You can find lots of other methods in the docs for different types such as int, 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]
    
  2. Class methods:
    In a similar way you can use map 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]
    
  3. 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 as get and it will be shorter but nobody really does that. Using attrgetter has an advantage, though, that you could take it out as a separate callable that could be used more than once (same as lambda):

      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 use operator.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 to add(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.

Georgy
  • 12,464
  • 7
  • 65
  • 73
  • 1
    Just wanted to point out that using unbound methods as arguments is a lot more restrictive than `lambda` or comprehensions, because you're now limited to one type only (or in case of self-defined classes that it duck-types correctly). For example `str.upper` will only work for strings, itwon't work if you have other types in the list, for example bytes or mixed type-lists. – MSeifert Jun 16 '19 at 14:49