'lambda' 关键字的更短替代方案?

Shorter alternative for 'lambda' keyword?

背景:

Python 是关于简单性和可读性的代码。它在各个版本中变得更好,我是它的超级粉丝!但是,每次我必须定义 lambda 时键入 l a m b d a 并不好玩(您可能不同意)。 问题是,这 6 个字符 l a m b d a 让我的语句变长,特别是如果我嵌套了几个 lambda在 maps 和 filters 内。 我没有嵌套超过 2 或 3 个,因为它带走了 python 的可读性,即使输入 l a m b d a感觉太啰嗦了

实际问题(在评论中):

# 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)

我很高兴像这样添加导入:

from myfuture import lmd_as_lambda
# OR
from myfuture import lambda_operator

好消息是:您根本不需要使用 mapfilter,您可以使用 generator expressions (lazy) or list comprehensions(渴望),从而避免 lambda 完全。

所以代替:

lines = map(lambda x: x.strip(), sys.stdin)

只需使用:

# 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)

如果你使用推导式,它可能也会更快。在 mapfilter 中使用时实际上很少有函数更快 - 使用 lambda 有更多的反模式(而且很慢)。


该问题仅包含 map 的示例,但您也可以替换 filter。例如,如果你想 filter 出奇数:

filter(lambda x: x%2==0, whatever)

您可以改用条件理解:

(x for x in whatever if x%2==0)
[x for x in whatever if x%2==0]

您甚至可以将 mapfilter 组合成一个理解:

(x*2 for x in whatever if x%2==0)

想想 mapfilter 会是什么样子:

map(lambda x: x*2, filter(lambda x: x%2==0, whatever))

注意:这并不意味着 lambda 没有用! lambda 在很多地方都非常方便。考虑 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。仅举几个 lambda 可能有用的示例,可能还有很多其他地方。

为了回答您的具体问题,operator 模块提供了几个旨在替换 lambda 表达式的特定用途的函数。在这里,您可以使用 methodcaller 函数创建一个调用对象上给定方法的函数。

from operator import methodcaller as mc

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

然而,列表推导式往往比 map.

的许多(如果不是大多数)用法更受欢迎
lines = [x.strip() for x in sys.stdin]

作为一个除了调试目的从不在代码中使用 lambda 的人,我可以提出几种替代方案。

我不会说 defining your own syntax in an editor (you can't define operators in a pure Python though: Python: defining my own operators?) 但只是 built-in 东西。

  1. built-in types的方法:
    比较以下内容:
    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']
    
    使用 mapstr.uppermaplambda 以及 .
    中提出的生成器表达式都要短 您可以在 docs 中找到许多针对不同类型的其他方法,例如 intfloatstrbytes 等,您可以在同样的方式。例如,检查数字是否为整数:
    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:
    以类似的方式,您可以将 map 与 class 方法一起使用:

    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模块:

    • itemgetter:
      当你想按索引 select 元素时使用这个:

      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]
      

      虽然它比给定示例中的生成器表达式长,但当您想要一次 select 多个元素时它实际上会更短:

      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)]
      

      您还可以将 itemgetter 与字典一起使用:

      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
      这个用于获取对象的属性:

      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)]
      

      它比生成器表达式版本更长,所以为了完整起见,我将其留在此处。当然,您可以将 attrgetter 导入为 get,它会更短,但没有人真正这样做。但是,使用 attrgetter 有一个优点,您可以将其作为单独的可调用对象取出,可以多次使用(与 lambda 相同):

      get_features = attrgetter('name', 'age', 'car')
      group_1_features = map(get_features, people)
      group_2_features = map(get_features, other_people)
      ...
      

      另一种值得一提的方法是使用 fget 属性方法:

      result = map(Person.age.fget, people)
      

      虽然我从未见过有人使用它,所以请准备好向使用它时会阅读您的代码的人解释。

    • contains:
      用于检查一个元素是否存在于另一个 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]
      

      不过,这有一个缺点,那就是要创建一个额外的 partial object. Another way to write this would be to use __contains__ 方法:

      result = map(fruits.__contains__, objects)
      

      但有些人认为使用 dunder 方法是一种不好的做法,因为这些方法仅供私人使用。

    • 数学运算:
      例如,如果您想对数字对求和,可以使用 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]
      

      如果您可以接受两个额外的导入,那么这是最短的选择。请注意,我们在这里使用 itertools.starmap 是因为我们需要在将数字元组提供给 add(a, b) 函数之前对其进行解包。


我认为我涵盖了我经常遇到的大多数情况,这些情况可以在没有 lambda 的情况下重写。如果你知道更多,请写在评论中,我会把它添加到我的答案中。