【问题标题】:E731 do not assign a lambda expression, use a defE731 不分配 lambda 表达式,使用 def
【发布时间】:2014-09-20 12:31:00
【问题描述】:

每当我使用 lambda 表达式时,都会收到此 pep8 警告。不推荐使用 lambda 表达式吗?如果不是为什么?

【问题讨论】:

  • 为清楚起见,该问题指的是自动签入flake8 (flake8.pycqa.org) 的消息

标签: python lambda flake8 pep


【解决方案1】:

您遇到的PEP-8 中的建议是:

始终使用 def 语句而不是赋值语句 将 lambda 表达式直接绑定到名称。

是的:

def f(x): return 2*x 

没有:

f = lambda x: 2*x 

第一种形式表示生成的名称 函数对象是专门的 'f' 而不是通用的 ''。 这对于回溯和字符串表示更有用 一般的。赋值语句的使用消除了唯一 lambda 表达式可以提供优于显式 def 语句的好处 (即它可以嵌入到更大的表达式中)

将 lambdas 分配给名称基本上只是复制了 def 的功能 - 通常,最好采用单一方式来避免混淆并提高清晰度。

lambda 的合法用例是你想使用一个函数而不分配它,例如:

sorted(players, key=lambda player: player.rank)

一般来说,反对这样做的主要论据是def 语句会导致更多的代码行。我对此的主要反应是:是的,这很好。除非你是打代码打高尔夫球,否则你不应该做的事情是尽量减少行数:要清楚而不是简短。

【讨论】:

  • 我看不出情况有多糟。回溯仍将包括错误的行号和源文件。一个人可能会说“f”,而另一个人会说“lambda”。也许 lambda 错误更容易扫描,因为它不是单字符函数名,也不是名称不佳的长名称?
  • @g33kz0r 好吧,当然,如果您假设其余代码质量较差,那么遵循约定不会给您带来太多好处。一般来说,不,这不是世界末日,但它仍然是个坏主意。
  • 这个答案不是很有帮助,因为当通过 PEP8 检查器运行使用def 的建议方法时,你会得到E704 multiple statements on one line (def),如果你把它分成两行,你会得到E301 expected 1 blank line, found 0 :-/
  • 我同意它应该被拆分。我的观点是a)它没有在上面的答案代码中拆分,导致E704,b)如果你拆分它,你需要在它上面有一个丑陋的空行以避免E301。
  • 当我想强调一个纯函数(没有副作用)时,我使用 lambdas,有时我必须在两个地方使用相同的函数,即 groupby 和 sort together。所以我忽略了这个约定。
【解决方案2】:

故事是这样的,我有一个简单的 lambda 函数,我使用了两次。

a = map(lambda x : x + offset, simple_list)
b = map(lambda x : x + offset, another_simple_list)

这只是为了表示,我遇到了几个不同的版本。

现在,为了保持干燥,我开始重用这个常见的 lambda。

f = lambda x : x + offset
a = map(f, simple_list)
b = map(f, another_simple_list)

此时我的代码质量检查器抱怨 lambda 是一个命名函数,所以我将它转换为一个函数。

def f(x):
    return x + offset
a = map(f, simple_list)
b = map(f, another_simple_list)

现在检查器抱怨一个函数必须在前后各有一个空行。

def f(x):
    return x + offset

a = map(f, simple_list)
b = map(f, another_simple_list)

这里我们现在有 6 行代码,而不是原来的 2 行代码,可读性没有增加,pythonic 也没有增加。此时代码检查器会抱怨该函数没有文档字符串。

在我看来,这条规则最好在有意义的时候避免和打破,使用你的判断。

【讨论】:

  • a = [x + offset for x in simple_list]。此处无需使用maplambda
  • @Georgy 我相信关键是将x + offset 部分移动到一个抽象位置,无需更改超过一行代码即可更新该位置。使用您提到的列表推导,您仍然需要两行包含x + offset 的代码,它们现在将位于列表推导中。为了按照作者的意愿提取这些内容,您需要 deflambda
  • @Julian 除了deflambda,还可以使用functools.partialf = partial(operator.add, offset),然后是a = list(map(f, simple_list))
  • def f(x): return x + offset(即在一行上定义的简单函数)怎么样?至少对于 flake8,我没有收到关于空行的抱怨。
  • @Julian 在某些情况下,您可以使用嵌套推导:a, b = [[x + offset for x lst] for lst in (simple_list, another_simple_list)]
【解决方案3】:

Lattyware 是绝对正确的:基本上PEP-8 希望你避免这样的事情

f = lambda x: 2 * x

改为使用

def f(x):
    return 2 * x

但是,正如最近的 bugreport(2014 年 8 月)中所述,以下声明现在是合规的:

a.f = lambda x: 2 * x
a["f"] = lambda x: 2 * x

由于我的 PEP-8 检查器还没有正确实现这一点,我暂时关闭了 E731。

【讨论】:

  • 即使使用def,PEP8 检查器也会报错E301 expected 1 blank line, found 0,所以你必须在它之前添加一个丑陋的空行。
【解决方案4】:

我还遇到了一个甚至无法使用定义(ined)函数的情况。

class SomeClass(object):
  # pep-8 does not allow this
  f = lambda x: x + 1  # NOQA

  def not_reachable(self, x):
    return x + 1

  @staticmethod
  def also_not_reachable(x):
    return x + 1

  @classmethod
  def also_not_reachable(cls, x):
    return x + 1

  some_mapping = {
      'object1': {'name': "Object 1", 'func': f},
      'object2': {'name': "Object 2", 'func': some_other_func},
  }

在这种情况下,我真的很想制作一个属于该类的映射。映射中的某些对象需要相同的功能。将命名函数放在类之外是不合逻辑的。 我还没有找到从类体内引用方法(静态方法、类方法或普通方法)的方法。运行代码时,SomeClass 尚不存在。所以从课堂上引用它也是不可能的。

【讨论】:

  • 您可以将映射定义中的also_not_reachable 称为SomeClass.also_not_reachable
  • 我不知道你想在这里表达什么。对我来说,在 2.7 和 3.5 中,您的每个函数名称都与 f 一样可访问
  • 不,除 lambda 函数外,所有函数都无法从类主体中访问。如果您尝试访问 some_mapping 对象中的这些函数之一,您将得到一个 AttributeError: type object 'SomeClass' has no attribute '...'。
  • @simP 所有这些都可以完全访问。带有@staticmethod@classmethod 的不需要对象,只需SomeClass.also_not_reachable(尽管它们需要独特的名称)。如果您需要从类方法中访问它们,只需使用 self.also_not_reachable
  • @simP 也许您应该将您的 *not_reachable 方法重命名为 not_as_easily_reachable_from_class_definition_as_a_lambda xD
【解决方案5】:

这对我在课堂上有效,删除 lambda 表达式并改用 def,改变这个...

    def set_every(self, every: int = 1, time_unit: int = TimeUnit.Day):
        every_func = lambda x: "*" if x == 1 else "*/" + str(x)
        if TimeUnit.has_value(time_unit):
            self.month_of_year = "*"
            self.day_of_month = "*" if time_unit != TimeUnit.Day else every_func(every)
            self.day_of_week = "*" if time_unit != TimeUnit.Week else every_func(every)

通过这个...

    def set_every(self, every: int = 1, time_unit: int = TimeUnit.Day):
        def every_func(x: int) -> str: return "*" if x == 1 else "*/" + str(x)
        if TimeUnit.has_value(time_unit):
            self.month_of_year = "*"
            self.day_of_month = "*" if time_unit != TimeUnit.Day else every_func(every)
            self.day_of_week = "*" if time_unit != TimeUnit.Week else every_func(every)

【讨论】:

  • 请添加更多详细信息以扩展您的答案,例如工作代码或文档引用。
  • OP 从未说过他的代码不起作用。这只是一个警告,因为它是一种非标准的编码实践
猜你喜欢
  • 2018-01-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-20
  • 1970-01-01
  • 2021-10-18
  • 2022-10-05
  • 2011-07-24
相关资源
最近更新 更多