E731 不要分配 lambda 表达式,请使用 def
- 2025-03-05 09:15:00
- admin 原创
- 41
问题描述:
每当我使用 lambda 表达式时,我都会收到此 pep8 警告。不推荐使用 lambda 表达式吗?如果不推荐,为什么?
解决方案 1:
您遇到的PEP-8中的建议是:
始终使用 def 语句,而不是将 lambda 表达式直接绑定到名称的赋值语句。
是的:
def f(x): return 2*x
不:
f = lambda x: 2*x
第一种形式意味着生成的函数对象的名称是“f”,而不是通用的“<lambda>”。这对于回溯和字符串表示通常更有用。赋值语句的使用消除了 lambda 表达式相对于显式 def 语句的唯一优势(即它可以嵌入更大的表达式中)
将 lambda 分配给名称基本上只是重复了功能def
- 并且通常,最好以单一方式执行某项操作以避免混淆并提高清晰度。
lambda 的合法用例是你想要使用一个函数而不对其进行赋值,例如:
sorted(players, key=lambda player: player.rank)
一般来说,反对这样做的主要理由是def
语句会导致代码行数增加。我对此的主要回应是:是的,这很好。除非您正在进行代码打磨,否则尽量减少代码行数并不是您应该做的事情:追求清晰而不是简短。
解决方案 2:
故事是这样的,我有一个简单的 lambda 函数,我使用了两次。
a = map(lambda x : x + offset, simple_list)
b = map(lambda x : x + offset, another_simple_list)
这只是为了表示,我已经遇到过几个不同的版本。
现在,为了让事情保持 DRY,我开始重用这个常见的 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 行,可读性没有提高,Python 风格也没有提高。此时,代码检查器会抱怨该函数没有文档字符串。
在我看来,最好避免这个规则,当它有意义时就打破它,运用你的判断力。
解决方案 3:
Lattyware 完全正确:基本上PEP-8希望你避免以下事情
f = lambda x: 2 * x
而是使用
def f(x):
return 2 * x
然而,正如最近的错误报告(2014 年 8 月)中所述,如下声明现在是合规的:
a.f = lambda x: 2 * x
a["f"] = lambda x: 2 * x
由于我的 PEP-8 检查器尚未正确实现这一点,因此我暂时关闭了 E731。
解决方案 4:
我还遇到过甚至无法使用定义函数的情况。
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 还不存在。因此从类中引用它也是不可能的。
解决方案 5:
主要原因似乎是使用 lambda 表达式创建新函数会降低代码的可读性,而使用 def 关键字创建这些函数则不行。如果您有一些代码,并且一些函数是用 def 创建的,而另一些函数是用 lambda 创建的,那么区分哪些是函数、哪些是变量可能会令人困惑。
简而言之,def 关键字用于创建新函数,而 lambda 用于匿名函数(即不与名称绑定的函数)。将变量定义为某个 lambda 表达式的结果似乎与 lambda 表达式的预期用例背道而驰。
解决方案 6:
如果您正在使用 mypy,那么选择 defs 的另一个原因是类型安全。
尽管以下代码包含类型错误,但它仍将通过 mypy 类型检查:
y = lambda x: x**2
print(y("fred"))
我们可以使用下面带注释的代码使其类型安全,现在 mypy 将按预期检测到错误。
from typing import Callable
y: Callable[[int], int] = lambda x: x**2
print(y("fred"))
但是,这看起来有点笨拙。让我们与下面的类型安全def
替代方案进行比较。
def y(x: int) -> int:
return x**2
print(y("fred"))
可以说,该def
版本更易读、更简洁(客观地说,虽然它占用了两行,但总体字符数更少,并且不需要额外导入)。
解决方案 7:
我最近遇到了一个非常合理的理由来直接分配lambda
表达式(一如既往,可以通过创建额外的代码来避免)。基本上,我需要在以后读取变量的值,但我还需要从中构造一些我使用的接口期望延迟评估的东西。
#my_module.py
from ... import get_global_config, Other
class Some(Other):
SOME_MAPPING = { ... }
def __init__(self, ...):
some_attr = lambda: SOME_MAPPING[
get_global_config().some_param
]
...
super().__init__(self, some_attr, ...)
当我构造对象时,global_config
所讨论的对象可能会或可能不会被设置/构造/完全配置,所以我需要将获取配置的值推迟到实际需要的时候。
在正常情况下,可以通过使用属性来避免这种情况,但是我正在使用/继承的接口要求我Callable[[], ...]
为特定属性分配一些具体的东西或类型的闭包,而我目前真的不想重构数千行代码。
解决方案 8:
这对我来说在一个类中起作用,删除 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)