Python 中没有多行 Lambda:为什么不呢?

发布于 2024-07-29 17:26:28 字数 264 浏览 8 评论 0原文

我听说不能在 Python 中添加多行 lambda,因为它们会与 Python 中的其他语法结构在语法上发生冲突。 今天我在公交车上思考这个问题,并意识到我想不出与多行 lambda 发生冲突的单个 Python 结构。 鉴于我非常了解这门语言,这让我感到惊讶。

现在,我确信 Guido 有理由不在语言中包含多行 lambda,但出于好奇:在什么情况下包含多行 lambda 会产生歧义? 我所听到的是否属实,还是有其他原因导致 Python 不允许多行 lambda 表达式?

I've heard it said that multiline lambdas can't be added in Python because they would clash syntactically with the other syntax constructs in Python. I was thinking about this on the bus today and realized I couldn't think of a single Python construct that multiline lambdas clash with. Given that I know the language pretty well, this surprised me.

Now, I'm sure Guido had a reason for not including multiline lambdas in the language, but out of curiosity: what's a situation where including a multiline lambda would be ambiguous? Is what I've heard true, or is there some other reason that Python doesn't allow multiline lambdas?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(26

淑女气质 2024-08-05 17:26:29

我对在我的一些项目中实践这种肮脏的黑客行为感到内疚,这有点简单:

    lambda args...:( expr1, expr2, expr3, ...,
            exprN, returnExpr)[-1]

我希望你能找到一种方法来保持Python风格,但如果你必须这样做,这比使用 exec 和操作全局变量更不痛苦。

I'm guilty of practicing this dirty hack in some of my projects which is bit simpler:

    lambda args...:( expr1, expr2, expr3, ...,
            exprN, returnExpr)[-1]

I hope you can find a way to stay pythonic but if you have to do it this less painful than using exec and manipulating globals.

扮仙女 2024-08-05 17:26:29

[编辑编辑] 因为这个问题在被问到 12 年后仍然活跃。 我将延续每四年左右修改一次答案的传统。

首先,问题是多行 lambda 如何与 Python 发生冲突。 接受的答案通过一个简单的例子展示了如何操作。 几年前我在下面链接的高度评价的答案回答了“为什么它不是 Python 的一部分”的问题——这个答案对于那些认为现有的“冲突”示例不足以使多-line lambda 不可能在 Python 中实现。

在这个答案的先前迭代中,我讨论了如何按原样将多行 lambda 实现到 Python 中。 我已经删除了那部分,因为这是一系列不好的做法。 如果您愿意,您可以在此答案的编辑历史记录中看到它。

然而,“为什么不?”的答案“因为罗森这么说”仍然可能令人沮丧。 因此,让我们看看它是否可以围绕用户 balpha 给出的计数器示例进行设计:

map(lambda x:
        y=x+1 # <-- this line defines the outmost indent level*
        for i in range(12):
            y+=12
        return y
   , [1,2,3])

#*By convention it is always one-indent past the 'l' in lambda

至于返回值,我们知道以下内容在 python 中是不允许的:

def f():
  return 3
, [1,2,3]

因此按照相同的逻辑,“[1,2,3]”应该不属于返回值的一部分。 让我们尝试这种方式:

map(lambda x:
        y=x+1                    # part of lambda block
        for i in range(12):      # part of lambda block
            y+=12                # part of lambda block
        return y, [1,2,3])       # part of lambda block

这个比较棘手,但由于 lambda 块有一个明确定义的开头(标记“lambda”)但没有明确的结尾,我认为作为 lambda 块一部分在同一行的任何内容都是也是 lambda 块的一部分。

人们可能会想象一些功能可以识别右括号,甚至可以根据封闭元素预期的标记数量进行推断。 一般来说,上面的表达式看起来并不是完全不可能解析,但可能有点挑战。

为了简化事情,您可以分隔所有不打算成为块一部分的字符:

map(lambda x:
        y=x+1                    # part of lambda block
        for i in range(12):      # part of lambda block
            y+=12                # part of lambda block
        return y                 # part of lambda block
, [1,2,3]) # argument separator, second argument, and closing paren for map

回到我们原来的位置,但这一次它是明确的,因为最后一行位于 lambda 块的最低缩进深度后面。
单行 lambda 是一种特殊情况(通过颜色后面缺少直接换行符来识别),其行为与现在相同。

这并不是说它一定应该成为 Python 的一部分——但它是一个快速说明,通过对语言进行一些更改也许是可能的。

[编辑]阅读这个答案。它解释了为什么多行 lambda 不是一件事。

简而言之,它是非Pythonic的。 来自 Guido van Rossum 的博客文章:

我发现任何在表达式中间嵌入基于缩进的块的解决方案都是不可接受的。 由于我发现语句分组的替代语法(例如大括号或开始/结束关键字)同样不可接受,这几乎使多行 lambda 成为一个无法解决的难题。

[Edit Edit] Since this question is somehow still active 12 years after being asked. I will continue the tradition of amending my answer every 4 years or so.

Firstly, the question was how does multi-line lambda clash with Python. The accepted answer shows how with a simple example. The highly rated answer I linked below some years ago answers the question of "Why is it not a part of Python"--this answer is perhaps more satisfying to those who believe that the existing examples of "clashing" are not enough to make multi-line lambda impossible to implement in Python.

In previous iterations of this answer I discussed how to implement multi-line lambda into Python as is. I've since removed that part, because it was a flurry of bad practices. You may see it in the edit history of this answer if you wish.

However the answer to "Why not?", being "because Rossum said so" can still be a source of frustration. So lets see if it could be engineered around the counter example given by user balpha:

map(lambda x:
        y=x+1 # <-- this line defines the outmost indent level*
        for i in range(12):
            y+=12
        return y
   , [1,2,3])

#*By convention it is always one-indent past the 'l' in lambda

As for the return value we have that the following is non-permissible in python:

def f():
  return 3
, [1,2,3]

So by the same logic, "[1,2,3]" should not be part of the return value. Let's try it this way instead:

map(lambda x:
        y=x+1                    # part of lambda block
        for i in range(12):      # part of lambda block
            y+=12                # part of lambda block
        return y, [1,2,3])       # part of lambda block

This one's trickier, but since the lambda block has a clearly defined beginning (the token 'lambda') yet no clear ending, I would argue anything that is on the same line as part of a lambda block is also part of the lambda block.

One might imagine some features that can identify closing parenthesis or even inference based on the number of tokens expected by the enclosing element. In general, the above expression does not seem totally impossible to parse, but it may be a bit of a challenge.

To simplify things, you could separate all characters not intended to be part of the block:

map(lambda x:
        y=x+1                    # part of lambda block
        for i in range(12):      # part of lambda block
            y+=12                # part of lambda block
        return y                 # part of lambda block
, [1,2,3]) # argument separator, second argument, and closing paren for map

Back to where we were but this time it is unambiguous, because the last line is behind the lowest indent-depth for the lambda block.
Single line lambda would be a special case (identified by the lack of an immediate newline after the color), that behaves the same as it does now.

This is not to say that it necessarily should be a part of Python--but it is a quick illustration that is perhaps is possible with some changes in the language.

[Edit] Read this answer. It explains why multi-line lambda is not a thing.

Simply put, it's unpythonic. From Guido van Rossum's blog post:

I find any solution unacceptable that embeds an indentation-based block in the middle of an expression. Since I find alternative syntax for statement grouping (e.g. braces or begin/end keywords) equally unacceptable, this pretty much makes a multi-line lambda an unsolvable puzzle.

晨曦÷微暖 2024-08-05 17:26:29

让我也对不同的解决方法提出我的两分钱。

简单的单行 lambda 与普通函数有何不同? 我只能想到缺少赋值、一些类似循环的结构(for、while)、try- except 子句……就这样吗? 我们甚至还有一个三元运算符 - 酷! 因此,让我们尝试解决这些问题。

作业

这里有些人正确地指出,我们应该看看 lisp 的 let 形式,它允许本地绑定。 实际上,所有非状态改变的赋值只能用let来执行。 但每个 Lisp 程序员都知道 let 形式绝对等同于调用 lambda 函数! 这意味着

(let ([x_ x] [y_ y])
  (do-sth-with-x-&-y x_ y_))

与 相同

((lambda (x_ y_)
   (do-sth-with-x-&-y x_ y_)) x y)

所以 lambda 就足够了! 每当我们想要进行新的分配时,我们只需添加另一个 lambda 并调用它即可。 考虑这个例子:

def f(x):
    y = f1(x)
    z = f2(x, y)
    return y,z

lambda 版本看起来像:

f = lambda x: (lambda y: (y, f2(x,y)))(f1(x))

如果您不喜欢在对数据执行操作之后写入数据,您甚至可以创建 let 函数。 你甚至可以柯里化它(只是为了更多的括号:))

let = curry(lambda args, f: f(*args))
f_lmb = lambda x: let((f1(x),), lambda y: (y, f2(x,y)))
# or:
f_lmb = lambda x: let((f1(x),))(lambda y: (y, f2(x,y)))

# even better alternative:
let = lambda *args: lambda f: f(*args)
f_lmb = lambda x: let(f1(x))(lambda y: (y, f2(x,y)))

到目前为止一切顺利。 但是如果我们必须重新分配,即改变状态怎么办? 好吧,我认为只要所讨论的任务不涉及循环,我们就可以在不改变状态的情况下绝对幸福地生活。

循环

虽然循环没有直接的 lambda 替代方案,但我相信我们可以编写非常通用的函数来满足我们的需求。 看看这个斐波那契函数:

def fib(n):
    k = 0
    fib_k, fib_k_plus_1 = 0, 1
    while k < n:
        k += 1
        fib_k_plus_1, fib_k = fib_k_plus_1 + fib_k, fib_k_plus_1
    return fib_k

显然,就 lambda 而言是不可能的。 但是在编写了一个小而有用的函数之后,我们就完成了这个和类似的情况:

def loop(first_state, condition, state_changer):
    state = first_state
    while condition(*state):
        state = state_changer(*state)
    return state

fib_lmb = lambda n:\
            loop(
              (0,0,1),
              lambda k, fib_k, fib_k_plus_1:\
                k < n,
              lambda k, fib_k, fib_k_plus_1:\
                (k+1, fib_k_plus_1, fib_k_plus_1 + fib_k))[1]

当然,人们应该始终考虑使用 mapreduce 和其他高阶函数如果可能的话。

Try- except 和其他控制结构

解决此类问题的一般方法似乎是利用惰性求值,用不接受参数的 lambda 替换代码块:

def f(x):
    try:    return len(x)
    except: return 0
# the same as:
def try_except_f(try_clause, except_clause):
    try: return try_clause()
    except: return except_clause()
f = lambda x: try_except_f(lambda: len(x), lambda: 0)
# f(-1) -> 0
# f([1,2,3]) -> 3

当然,这并不是 try- except 子句的完整替代方案,但你总是可以让它更通用。 顺便说一句,通过这种方法,您甚至可以使 if 表现得像函数!

总结:很自然地,所提到的一切都让人感觉有点不自然并且不那么漂亮。 尽管如此——它有效! 并且没有任何 evals 和其他技巧,因此所有智能感知都将起作用。 我也不是说你应该在任何地方使用它。 大多数情况下,您最好定义一个普通函数。 我只是表明没有什么是不可能的。

Let me also throw in my two cents about different workarounds.

How is a simple one-line lambda different from a normal function? I can think only of lack of assignments, some loop-like constructs (for, while), try-except clauses... And that's it? We even have a ternary operator - cool! So, let's try to deal with each of these problems.

Assignments

Some guys here have rightly noted that we should take a look at lisp's let form, which allows local bindings. Actually, all the non state-changing assignments can be performed only with let. But every lisp programmer knows that let form is absolutely equivalent to call to a lambda function! This means that

(let ([x_ x] [y_ y])
  (do-sth-with-x-&-y x_ y_))

is the same as

((lambda (x_ y_)
   (do-sth-with-x-&-y x_ y_)) x y)

So lambdas are more than enough! Whenever we want to make a new assignment we just add another lambda and call it. Consider this example:

def f(x):
    y = f1(x)
    z = f2(x, y)
    return y,z

A lambda version looks like:

f = lambda x: (lambda y: (y, f2(x,y)))(f1(x))

You can even make the let function, if you don't like the data being written after actions on the data. And you can even curry it (just for the sake of more parentheses :) )

let = curry(lambda args, f: f(*args))
f_lmb = lambda x: let((f1(x),), lambda y: (y, f2(x,y)))
# or:
f_lmb = lambda x: let((f1(x),))(lambda y: (y, f2(x,y)))

# even better alternative:
let = lambda *args: lambda f: f(*args)
f_lmb = lambda x: let(f1(x))(lambda y: (y, f2(x,y)))

So far so good. But what if we have to make reassignments, i.e. change state? Well, I think we can live absolutely happily without changing state as long as task in question doesn't concern loops.

Loops

While there's no direct lambda alternative for loops, I believe we can write quite generic function to fit our needs. Take a look at this fibonacci function:

def fib(n):
    k = 0
    fib_k, fib_k_plus_1 = 0, 1
    while k < n:
        k += 1
        fib_k_plus_1, fib_k = fib_k_plus_1 + fib_k, fib_k_plus_1
    return fib_k

Impossible in terms of lambdas, obviously. But after writing a little yet useful function we're done with that and similar cases:

def loop(first_state, condition, state_changer):
    state = first_state
    while condition(*state):
        state = state_changer(*state)
    return state

fib_lmb = lambda n:\
            loop(
              (0,0,1),
              lambda k, fib_k, fib_k_plus_1:\
                k < n,
              lambda k, fib_k, fib_k_plus_1:\
                (k+1, fib_k_plus_1, fib_k_plus_1 + fib_k))[1]

And of course, one should always consider using map, reduce and other higher-order functions if possible.

Try-except and other control structs

It seems like a general approach to this kind of problems is to make use of lazy evaluation, replacing code blocks with lambdas accepting no arguments:

def f(x):
    try:    return len(x)
    except: return 0
# the same as:
def try_except_f(try_clause, except_clause):
    try: return try_clause()
    except: return except_clause()
f = lambda x: try_except_f(lambda: len(x), lambda: 0)
# f(-1) -> 0
# f([1,2,3]) -> 3

Of course, this is not a full alternative to try-except clause, but you can always make it more generic. Btw, with that approach you can even make if behave like function!

Summing up: it's only natural that everything mentioned feels kinda unnatural and not-so-pythonically-beautiful. Nonetheless - it works! And without any evals and other trics, so all the intellisense will work. I'm also not claiming that you shoud use this everywhere. Most often you'd better define an ordinary function. I only showed that nothing is impossible.

情栀口红 2024-08-05 17:26:29

Python3.8之后,还有一种本地绑定的方法

lambda x: (
    y := x + 1,
    y ** 2
)[-1]

For Loop

lambda x: (
    y := x ** 2,
    [y := y + x for _ in range(10)],
    y
)[-1]

If Branch

lambda x: (
    y := x ** 2,
    x > 5 and [y := y + x for _ in range(10)],
    y
)[-1]

Or

lambda x: (
    y := x ** 2,
    [y := y + x for _ in range(10)] if x > 5 else None,
    y
)[-1]

While Loop

import itertools as it
lambda x: (
    l := dict(y = x ** 2),
    cond := lambda: l['y'] < 100,
    body := lambda: l.update(y = l['y'] + x),
    *it.takewhile(lambda _: cond() and (body(), True)[-1], it.count()),
    l['y']
)[-1]

Or

import itertools as it
from types import SimpleNamespace as ns
lambda x: (
    l := ns(y = x ** 2),
    cond := lambda: l.y < 100,
    body := lambda: vars(l).update(y = l.y + x),
    *it.takewhile(lambda _: cond() and (body(), True)[-1], it.count()),
    l.y
)[-1]

Or

import itertools as it
lambda x: (
    y := x ** 2,
    *it.takewhile(lambda t: t[0],
    ((
    pred := y < 100,
    pred and (y := y + x))
    for _ in it.count())),
    y
)[-1]

After Python3.8, there is another method for local binding

lambda x: (
    y := x + 1,
    y ** 2
)[-1]

For Loop

lambda x: (
    y := x ** 2,
    [y := y + x for _ in range(10)],
    y
)[-1]

If Branch

lambda x: (
    y := x ** 2,
    x > 5 and [y := y + x for _ in range(10)],
    y
)[-1]

Or

lambda x: (
    y := x ** 2,
    [y := y + x for _ in range(10)] if x > 5 else None,
    y
)[-1]

While Loop

import itertools as it
lambda x: (
    l := dict(y = x ** 2),
    cond := lambda: l['y'] < 100,
    body := lambda: l.update(y = l['y'] + x),
    *it.takewhile(lambda _: cond() and (body(), True)[-1], it.count()),
    l['y']
)[-1]

Or

import itertools as it
from types import SimpleNamespace as ns
lambda x: (
    l := ns(y = x ** 2),
    cond := lambda: l.y < 100,
    body := lambda: vars(l).update(y = l.y + x),
    *it.takewhile(lambda _: cond() and (body(), True)[-1], it.count()),
    l.y
)[-1]

Or

import itertools as it
lambda x: (
    y := x ** 2,
    *it.takewhile(lambda t: t[0],
    ((
    pred := y < 100,
    pred and (y := y + x))
    for _ in it.count())),
    y
)[-1]
落叶缤纷 2024-08-05 17:26:29

让我尝试解决@balpha 解析问题。 我会在多行 lamda 周围使用括号。 如果没有括号,则 lambda 定义是贪婪的。 因此, in 中的 lambda

map(lambda x:
      y = x+1
      z = x-1
      y*z,
    [1,2,3]))

返回一个返回 (y*z, [1,2,3]) 的函数,

map((lambda x:
      y = x+1
      z = x-1
      y*z)
    ,[1,2,3]))

意味着

map(func, [1,2,3])

其中 func 是返回 y*z 的多行 lambda。 那样有用吗?

Let me try to tackle @balpha parsing problem. I would use parentheses around the multiline lamda. If there is no parentheses, the lambda definition is greedy. So the lambda in

map(lambda x:
      y = x+1
      z = x-1
      y*z,
    [1,2,3]))

returns a function that returns (y*z, [1,2,3])

But

map((lambda x:
      y = x+1
      z = x-1
      y*z)
    ,[1,2,3]))

means

map(func, [1,2,3])

where func is the multiline lambda that return y*z. Does that work?

缱绻入梦 2024-08-05 17:26:29

(对于任何仍然对该主题感兴趣的人。)

考虑这一点(甚至包括在“多行”lambda 中的进一步语句中使用语句的返回值,尽管它丑陋到令人呕吐的地步;-)

>>> def foo(arg):
...     result = arg * 2;
...     print "foo(" + str(arg) + ") called: " + str(result);
...     return result;
...
>>> f = lambda a, b, state=[]: [
...     state.append(foo(a)),
...     state.append(foo(b)),
...     state.append(foo(state[0] + state[1])),
...     state[-1]
... ][-1];
>>> f(1, 2);
foo(1) called: 2
foo(2) called: 4
foo(6) called: 12
12

(For anyone still interested in the topic.)

Consider this (includes even usage of statements' return values in further statements within the "multiline" lambda, although it's ugly to the point of vomiting ;-)

>>> def foo(arg):
...     result = arg * 2;
...     print "foo(" + str(arg) + ") called: " + str(result);
...     return result;
...
>>> f = lambda a, b, state=[]: [
...     state.append(foo(a)),
...     state.append(foo(b)),
...     state.append(foo(state[0] + state[1])),
...     state[-1]
... ][-1];
>>> f(1, 2);
foo(1) called: 2
foo(2) called: 4
foo(6) called: 12
12
凌乱心跳 2024-08-05 17:26:29

这是多行 lambda 的更有趣的实现。 这是不可能实现的,因为 python 使用缩进作为构建代码的方式。

但幸运的是,可以使用数组和括号禁用缩进格式。

正如一些人已经指出的那样,您可以这样编写代码:

lambda args: (expr1, expr2,... exprN)

理论上,如果保证从左到右进行求值,那么它会起作用,但您仍然会丢失从一个表达式传递到另一个表达式的值。

实现更冗长的一种方法是让

lambda args: [lambda1, lambda2, ..., lambdaN]

每个 lambda 接收前一个 lambda 的参数。

def let(*funcs):
    def wrap(args):
        result = args                                                                                                                                                                                                                         
        for func in funcs:
            if not isinstance(result, tuple):
                result = (result,)
            result = func(*result)
        return result
    return wrap

这个方法可以让你写一些有点像 lisp/scheme 的东西。

因此,您可以编写如下内容:

let(lambda x, y: x+y)((1, 2))

可以使用更复杂的方法来计算斜边

lst = [(1,2), (2,3)]
result = map(let(
  lambda x, y: (x**2, y**2),
  lambda x, y: (x + y) ** (1/2)
), lst)

这将返回标量数字列表,因此可用于将多个值减少为一个。

拥有这么多 lambda 肯定不会非常高效,但如果您受到限制,这可能是快速完成某件事然后稍后将其重写为实际函数的好方法。

Here's a more interesting implementation of multi line lambdas. It's not possible to achieve because of how python use indents as a way to structure code.

But luckily for us, indent formatting can be disabled using arrays and parenthesis.

As some already pointed out, you can write your code as such:

lambda args: (expr1, expr2,... exprN)

In theory if you're guaranteed to have evaluation from left to right it would work but you still lose values being passed from one expression to an other.

One way to achieve that which is a bit more verbose is to have

lambda args: [lambda1, lambda2, ..., lambdaN]

Where each lambda receives arguments from the previous one.

def let(*funcs):
    def wrap(args):
        result = args                                                                                                                                                                                                                         
        for func in funcs:
            if not isinstance(result, tuple):
                result = (result,)
            result = func(*result)
        return result
    return wrap

This method let you write something that is a bit lisp/scheme like.

So you can write things like this:

let(lambda x, y: x+y)((1, 2))

A more complex method could be use to compute the hypotenuse

lst = [(1,2), (2,3)]
result = map(let(
  lambda x, y: (x**2, y**2),
  lambda x, y: (x + y) ** (1/2)
), lst)

This will return a list of scalar numbers so it can be used to reduce multiple values to one.

Having that many lambda is certainly not going to be very efficient but if you're constrained it can be a good way to get something done quickly then rewrite it as an actual function later.

梦开始←不甜 2024-08-05 17:26:29

在Python 3.8/3.9中,有赋值表达式,所以它可以在lambda中使用,极大地
扩展功能

例如,代码

#%%
x = 1
y = 2

q = list(map(lambda t: (
    tx := t*x,
    ty := t*y,
    tx+ty
)[-1], [1, 2, 3]))

print(q)

将打印 [3, 6, 9]

In Python 3.8/3.9 there is Assignment Expression, so it could be used in lambda, greatly
expanding functionality

E.g., code

#%%
x = 1
y = 2

q = list(map(lambda t: (
    tx := t*x,
    ty := t*y,
    tx+ty
)[-1], [1, 2, 3]))

print(q)

will print [3, 6, 9]

腻橙味 2024-08-05 17:26:29

关于丑陋的黑客,您始终可以使用 exec 和常规函数的组合来定义如下所示的多行函数:

f = exec('''
def mlambda(x, y):
    d = y - x
    return d * d
''', globals()) or mlambda

您可以将其包装到如下函数中:

def mlambda(signature, *lines):
    exec_vars = {}
    exec('def mlambda' + signature + ':\n' + '\n'.join('\t' + line for line in lines), exec_vars)
    return exec_vars['mlambda']

f = mlambda('(x, y)',
            'd = y - x',
            'return d * d')

On the subject of ugly hacks, you can always use a combination of exec and a regular function to define a multiline function like this:

f = exec('''
def mlambda(x, y):
    d = y - x
    return d * d
''', globals()) or mlambda

You can wrap this into a function like:

def mlambda(signature, *lines):
    exec_vars = {}
    exec('def mlambda' + signature + ':\n' + '\n'.join('\t' + line for line in lines), exec_vars)
    return exec_vars['mlambda']

f = mlambda('(x, y)',
            'd = y - x',
            'return d * d')
棒棒糖 2024-08-05 17:26:29

我知道这是一个老问题,但这里记录的是多行 lambda 问题的一种解决方案,其中一个调用的结果被另一个调用消耗。

我希望它不是超级黑客,因为它仅基于标准库函数并且不使用 dunder 方法。

下面是一个简单的示例,我们从 x = 3 开始,然后在第一行添加 1,然后在第二行添加 2 code> 并获取 6 作为输出。

from functools import reduce

reduce(lambda data, func: func(data), [
    lambda x: x + 1,
    lambda x: x + 2
], 3)

## Output: 6

I know it is an old question, but for the record here is a kind of a solution to the problem of multiline lambda problem in which the result of one call is consumed by another call.

I hope it is not super hacky, since it is based only on standard library functions and uses no dunder methods.

Below is a simple example in which we start with x = 3 and then in the first line we add 1 and then in the second line we add 2 and get 6 as the output.

from functools import reduce

reduce(lambda data, func: func(data), [
    lambda x: x + 1,
    lambda x: x + 2
], 3)

## Output: 6
请爱~陌生人 2024-08-05 17:26:29

我只是玩了一下尝试用reduce 进行字典理解,并提出了这个线性黑客:

In [1]: from functools import reduce
In [2]: reduce(lambda d, i: (i[0] < 7 and d.__setitem__(*i[::-1]), d)[-1], [{}, *{1:2, 3:4, 5:6, 7:8}.items()])                                                                                                                                                                 
Out[3]: {2: 1, 4: 3, 6: 5}

我只是尝试做与Javascript 字典理解中所做的相同的事情: https://stackoverflow.com/a/11068265

I was just playing a bit to try to make a dict comprehension with reduce, and come up with this one liner hack:

In [1]: from functools import reduce
In [2]: reduce(lambda d, i: (i[0] < 7 and d.__setitem__(*i[::-1]), d)[-1], [{}, *{1:2, 3:4, 5:6, 7:8}.items()])                                                                                                                                                                 
Out[3]: {2: 1, 4: 3, 6: 5}

I was just trying to do the same as what was done in this Javascript dict comprehension: https://stackoverflow.com/a/11068265

迟月 2024-08-05 17:26:29

如果 lambda 函数有多行,则可以简单地使用斜杠 (\)

示例:

mx = lambda x, y: x if x > y \
     else y
print(mx(30, 20))

Output: 30

You can simply use slash (\) if you have multiple lines for your lambda function

Example:

mx = lambda x, y: x if x > y \
     else y
print(mx(30, 20))

Output: 30
挽梦忆笙歌 2024-08-05 17:26:29

我从 python 开始,但来自 Javascript,最明显的方法是将表达式提取为函数......

人为的示例,乘法表达式 (x*2) 被提取为函数,因此我可以使用多行:

def multiply(x):
  print('I am other line')
  return x*2

r = map(lambda x : multiply(x), [1, 2, 3, 4])
print(list(r))

https://repl.it/@datracka/python-lambda-function

如果这是如何在 lambda 表达式本身中执行多行,也许它不能准确回答问题,但如果有人得到这个线程寻找如何调试表达式(像我一样),我认为它会帮助

I am starting with python but coming from Javascript the most obvious way is extract the expression as a function....

Contrived example, multiply expression (x*2) is extracted as function and therefore I can use multiline:

def multiply(x):
  print('I am other line')
  return x*2

r = map(lambda x : multiply(x), [1, 2, 3, 4])
print(list(r))

https://repl.it/@datracka/python-lambda-function

Maybe it does not answer exactly the question if that was how to do multiline in the lambda expression itself, but in case somebody gets this thread looking how to debug the expression (like me) I think it will help

苄①跕圉湢 2024-08-05 17:26:29

我们可以使用内部类来获取多语句 lambda:

class Foo():
    def __init__(self, x):
        self.x=x

def test1(i):
    i.x += 1
    class closure():
        @staticmethod
        def call():
          i.x += 1
          print('lamba one value:', i.x)
    return closure.call

def test2(i):
    class closure():
        @staticmethod
        def call():
          i.x += 1
          print('lambda two value:', i.x)
    return closure.call

u = Foo(10)
test1(u)() # print 12
u = Foo(20)
test2(u)() # print 21

We can use inner classes to obtain multi-statement lambdas:

class Foo():
    def __init__(self, x):
        self.x=x

def test1(i):
    i.x += 1
    class closure():
        @staticmethod
        def call():
          i.x += 1
          print('lamba one value:', i.x)
    return closure.call

def test2(i):
    class closure():
        @staticmethod
        def call():
          i.x += 1
          print('lambda two value:', i.x)
    return closure.call

u = Foo(10)
test1(u)() # print 12
u = Foo(20)
test2(u)() # print 21
情绪 2024-08-05 17:26:29

我的黑客解决方案如何实现多行 lambda:

import re


def inline_func(code: str, globals_=None, locals_=None):
    if globals_ is None: globals_ = globals()
    if locals_ is None: locals_ = locals()

    lines = code.splitlines(keepends=True)
    indent = None
    func_name = None
    for i, line in enumerate(lines):
        if indent is None:
            if m := re.match(r'^(\s*)def\s+(\w+)', line):
                indent, func_name = m.groups()
                lines[i] = line.replace(indent, '', 1)
        else:
            lines[i] = line.replace(indent, '', 1)

    code = ''.join(lines).strip()
    exec(code, globals_, locals_)
    return locals_[func_name]


assert list(map(inline_func(
    '''
    def f(x):
        return (x + 1) ** 2
    '''
    ),
    range(3)
)) == [1, 4, 9]

它采用函数的文本定义,创建真正的可调用函数并返回它。 而且像 Pycharm 这样的 IDE 可以将语法高亮注入到字符串中,这个解决方案甚至没有那么糟糕,因为它可以像普通代码一样进行编辑:)

My hacky solution how to achieve multiline lambdas:

import re


def inline_func(code: str, globals_=None, locals_=None):
    if globals_ is None: globals_ = globals()
    if locals_ is None: locals_ = locals()

    lines = code.splitlines(keepends=True)
    indent = None
    func_name = None
    for i, line in enumerate(lines):
        if indent is None:
            if m := re.match(r'^(\s*)def\s+(\w+)', line):
                indent, func_name = m.groups()
                lines[i] = line.replace(indent, '', 1)
        else:
            lines[i] = line.replace(indent, '', 1)

    code = ''.join(lines).strip()
    exec(code, globals_, locals_)
    return locals_[func_name]


assert list(map(inline_func(
    '''
    def f(x):
        return (x + 1) ** 2
    '''
    ),
    range(3)
)) == [1, 4, 9]

It takes textual definition of a function, creates real callable function and returns it. And with IDEs like Pycharm which can inject syntax highlighting into strings, this solution is not even that bad as it can be edited as normal code:)

神经大条 2024-08-05 17:26:29

不是多行而是多操作 lambda(适用于 python 3.8+)

>>> foo = lambda x: (x := x**2, x + 1)[-1] # f = x^2 + 1
>>> foo(4)
17

Not multiline but multioperation lambda (working on python 3.8+)

>>> foo = lambda x: (x := x**2, x + 1)[-1] # f = x^2 + 1
>>> foo(4)
17
难理解 2024-08-05 17:26:29

我找到了一个相当不错的适合我的解决方法:因为 Python 是完全动态的,所以您可以重用 def! 它不是那么简洁,但它是退而求其次的。 例如:

thing1 = Thing()
def doThing():
  pass
thing1.func = doThing

thing2 = Thing()
def doThing():
  print("thingy")
thing2.func = doThing

在这种情况下,doThing 的作用就像一个多行lambda。 同样很酷的是,如果您不愿意,您甚至不必在此处的 Thing 类中定义 func 或做任何准备工作。

换句话说,你有一个完整的 装饰器模式 + Mixin Pattern 通过这种方式组合功能,因此您可以设置完全动态创建的类,其结构尽可能少或多欲望!

但对于那些只想在 Python 中执行多行 lambda 的人来说,使用这种方法的主要原因是因为现在您可以以半内联的方式分配一堆不同的函数,而不必为每个都想出聪明的名字。

I found a fairly nice workaround that works for me: Because Python is fully dynamic, you can re-use def's! It's not quite as terse, but it's the next best thing. E.g.:

thing1 = Thing()
def doThing():
  pass
thing1.func = doThing

thing2 = Thing()
def doThing():
  print("thingy")
thing2.func = doThing

So in this case, doThing is acting like a multi-line lambda. What's also cool is that you don't even have to define func or do any preparation work in the Thing class here if you don't want to.

In other words, you have a full-on Decorator Pattern + Mixin Pattern combo capability this way, so you can setup fully dynamically-created classes with as little or as much structure to them as you desire!

But the main reason to use this approach, for those looking just to do multi-line lambda's in Python, is because now you can assign a bunch of different functions in a semi-inline sort of way, without having to come up with clever names for each.

江南烟雨〆相思醉 2024-08-05 17:26:29

一些解决方案已经指出,如何使用 python 3.8+ 赋值表达式。 为了对此进行扩展,现在可以通过多行 lambda 来帮助类型检查器(python 3.12+):

Ts = TypeVarTuple("Ts")
class Lambda[**P, R]:
    def __init__(self, c: Callable[P, tuple[*Ts, R]]):
        self.c = c

    def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:
        return self.c(*args, **kwargs)[-1]

l = Lambda[[Iterable, int], int](lambda it, y: (
    it := [y*abs(z) for z in it],
    reduce(lambda a, b: a + b, it)
    ))

print(l(range(5), 2))

我也喜欢首先指定参数,如果我直接执行 lambda,它会与顺序很好地配合 需要做的

Ts = TypeVarTuple("Ts")
class L2[**P, R]:
    def __init__(self, *args: P.args, **kwargs: P.kwargs):
        self.args = args
        self.kwargs = kwargs

    def __call__(self, c: Callable[P, tuple[*Ts, R]]) -> R:
        return c(*self.args, **self.kwargs)[-1]

x = L2[[int, int], int](2, 3)(lambda x, y: (
    z := x + y,
    x * z
    ))

另一个注释:如果您不小心弄乱了赋值运算符 = 而不是 := 以及语法中可能的其他内容,您可能会变得毫无帮助错误消息,因此请注意。

Some solutions already point out, how assignments can be done in multiline lamdas with pythons 3.8+ assignment expressions. To extend a bit on this, now here is what one can do help the type-checker with multiline lambdas (python 3.12+):

Ts = TypeVarTuple("Ts")
class Lambda[**P, R]:
    def __init__(self, c: Callable[P, tuple[*Ts, R]]):
        self.c = c

    def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:
        return self.c(*args, **kwargs)[-1]

l = Lambda[[Iterable, int], int](lambda it, y: (
    it := [y*abs(z) for z in it],
    reduce(lambda a, b: a + b, it)
    ))

print(l(range(5), 2))

I also like to specify the parameters first, if I execute the lambda directly, it goes nice with the order of things

Ts = TypeVarTuple("Ts")
class L2[**P, R]:
    def __init__(self, *args: P.args, **kwargs: P.kwargs):
        self.args = args
        self.kwargs = kwargs

    def __call__(self, c: Callable[P, tuple[*Ts, R]]) -> R:
        return c(*self.args, **self.kwargs)[-1]

x = L2[[int, int], int](2, 3)(lambda x, y: (
    z := x + y,
    x * z
    ))

One other annotation to make: If You accidentially mess up with the assignment operators = insteand of := and possibly other things in the syntax, You will probably get quite unhelpful error messages, so be warned.

你怎么这么可爱啊 2024-08-05 17:26:29

一种在 lambda 项之间传递任意数量的变量的安全方法:

print((lambda: [
    locals().__setitem__("a", 1),
    locals().__setitem__("b", 2),
    locals().__setitem__("c", 3),
    locals().get("a") + locals().get("b") + locals().get("c")
])()[-1])

输出:6

One safe method to pass any number of variables between lambda items:

print((lambda: [
    locals().__setitem__("a", 1),
    locals().__setitem__("b", 2),
    locals().__setitem__("c", 3),
    locals().get("a") + locals().get("b") + locals().get("c")
])()[-1])

Output: 6

淡淡的优雅 2024-08-05 17:26:29

Python 中确实存在多行 lambda。

使用 Python,每当您需要多行时,请考虑括号

lambda a, b, c, d: (
    a + b + c + d
)

简短的例子:

l = [1, 2, 3, 4]

print(
    list(
        map(
            lambda element: (
                2 * element + 4
            ),
            l,
        )
    )
)

Multiline lambdas do exist in Python.

With Python, whenever you want multiline , think parenthesis.

lambda a, b, c, d: (
    a + b + c + d
)

Short example:

l = [1, 2, 3, 4]

print(
    list(
        map(
            lambda element: (
                2 * element + 4
            ),
            l,
        )
    )
)
屌丝范 2024-08-05 17:26:29

因为 lambda 函数应该是单行的,因为它是函数的最简单形式,入口,然后返回

because a lambda function is supposed to be one-lined, as its the simplest form of a function, an entrance, then return

別甾虛僞 2024-08-05 17:26:28

Guido van Rossum(Python 的发明者)在一篇旧博客文章中亲自回答了这个确切的问题
基本上,他承认这在理论上是可能的,但任何提出的解决方案都不是 Pythonic 的:

“但是对我来说,任何针对这个难题提出的解决方案的复杂性都是巨大的:它要求解析器(或更准确地说,词法分析器)能够在缩进敏感和缩进不敏感模式之间来回切换,保留一堆以前的模式和缩进级别从技术上讲,这一切都可以解决(已经有一堆可以概括的缩进级别),但这些都没有消除我的直觉,这都是一个精心设计的 鲁布·戈德堡装置。"

Guido van Rossum (the inventor of Python) answers this exact question himself in an old blog post.
Basically, he admits that it's theoretically possible, but that any proposed solution would be un-Pythonic:

"But the complexity of any proposed solution for this puzzle is immense, to me: it requires the parser (or more precisely, the lexer) to be able to switch back and forth between indent-sensitive and indent-insensitive modes, keeping a stack of previous modes and indentation level. Technically that can all be solved (there's already a stack of indentation levels that could be generalized). But none of that takes away my gut feeling that it is all an elaborate Rube Goldberg contraption."

撩人痒 2024-08-05 17:26:28

看一下下面的内容:

map(multilambda x:
      y=x+1
      return y
   , [1,2,3])

这是一个返回 (y, [1,2,3]) 的 lambda(因此 map 只获取一个参数,导致错误)? 或者它返回y? 或者这是一个语法错误,因为新行上的逗号放错了位置? Python 如何知道你想要什么?

在括号内,缩进对 python 来说并不重要,因此您不能明确地使用多行。

这只是一个简单的例子,可能还有更多的例子。

Look at the following:

map(multilambda x:
      y=x+1
      return y
   , [1,2,3])

Is this a lambda returning (y, [1,2,3]) (thus map only gets one parameter, resulting in an error)? Or does it return y? Or is it a syntax error, because the comma on the new line is misplaced? How would Python know what you want?

Within the parens, indentation doesn't matter to python, so you can't unambiguously work with multilines.

This is just a simple one, there's probably more examples.

够运 2024-08-05 17:26:28

这通常非常难看(但有时替代方案甚至更难看),因此解决方法是制作一个大括号表达式:

lambda: (
    doFoo('abc'),
    doBar(123),
    doBaz())

但它不会接受任何赋值,因此您必须事先准备数据。
我发现这个有用的地方是 PySide 包装器,有时您会在其中进行简短的回调。 编写额外的成员函数会更加难看。 通常你不需要这个。

例子:

pushButtonShowDialog.clicked.connect(
    lambda: (
    field1.clear(),
    spinBox1.setValue(0),
    diag.show())

This is generally very ugly (but sometimes the alternatives are even more ugly), so a workaround is to make a braces expression:

lambda: (
    doFoo('abc'),
    doBar(123),
    doBaz())

It won't accept any assignments though, so you'll have to prepare data beforehand.
The place I found this useful is the PySide wrapper, where you sometimes have short callbacks. Writing additional member functions would be even more ugly. Normally you won't need this.

Example:

pushButtonShowDialog.clicked.connect(
    lambda: (
    field1.clear(),
    spinBox1.setValue(0),
    diag.show())
弥繁 2024-08-05 17:26:28

一些相关链接:

有一段时间,我一直在关注 Reia 的开发,它最初也将采用 Python 的基于缩进的语法和 Ruby 块,所有这些都建立在 Erlang 之上。 但是,设计师最终放弃了缩进敏感性,他写的关于该决定的这篇文章包括对他在缩进+多行块方面遇到的问题的讨论,以及他对 Guido 的设计问题/决定的日益赞赏:

< a href="http://www.unlimitednovelty.com/2009/03/indentation-sensitivity-post-mortem.html" rel="noreferrer">http://www.unlimitednovelty.com/2009/03/indentation- sensitivity-post-mortem.html

另外,这里有一个关于 Python 中 Ruby 风格块的有趣提案,我遇到过 Guido 发布了一个响应,但没有实际将其击落(不确定是否有后续的击落) ,不过):

http://tav.espians.com/ruby-style -blocks-in-python.html

A couple of relevant links:

For a while, I was following the development of Reia, which was initially going to have Python's indentation based syntax with Ruby blocks too, all on top of Erlang. But, the designer wound up giving up on indentation sensitivity, and this post he wrote about that decision includes a discussion about problems he ran into with indentation + multi-line blocks, and an increased appreciation he gained for Guido's design issues/decisions:

http://www.unlimitednovelty.com/2009/03/indentation-sensitivity-post-mortem.html

Also, here's an interesting proposal for Ruby-style blocks in Python I ran across where Guido posts a response w/o actually shooting it down (not sure whether there has been any subsequent shoot down, though):

http://tav.espians.com/ruby-style-blocks-in-python.html

箜明 2024-08-05 17:26:28

让我向您展示一个光荣但可怕的黑客:

import types

def _obj():
  return lambda: None

def LET(bindings, body, env=None):
  '''Introduce local bindings.
  ex: LET(('a', 1,
           'b', 2),
          lambda o: [o.a, o.b])
  gives: [1, 2]

  Bindings down the chain can depend on
  the ones above them through a lambda.
  ex: LET(('a', 1,
           'b', lambda o: o.a + 1),
          lambda o: o.b)
  gives: 2
  '''
  if len(bindings) == 0:
    return body(env)

  env = env or _obj()
  k, v = bindings[:2]
  if isinstance(v, types.FunctionType):
    v = v(env)

  setattr(env, k, v)
  return LET(bindings[2:], body, env)

您现在可以使用这个 LET 形式:

map(lambda x: LET(('y', x + 1,
                   'z', x - 1),
                  lambda o: o.y * o.z),
    [1, 2, 3])

它给出:[0, 3, 8]

Let me present to you a glorious but terrifying hack:

import types

def _obj():
  return lambda: None

def LET(bindings, body, env=None):
  '''Introduce local bindings.
  ex: LET(('a', 1,
           'b', 2),
          lambda o: [o.a, o.b])
  gives: [1, 2]

  Bindings down the chain can depend on
  the ones above them through a lambda.
  ex: LET(('a', 1,
           'b', lambda o: o.a + 1),
          lambda o: o.b)
  gives: 2
  '''
  if len(bindings) == 0:
    return body(env)

  env = env or _obj()
  k, v = bindings[:2]
  if isinstance(v, types.FunctionType):
    v = v(env)

  setattr(env, k, v)
  return LET(bindings[2:], body, env)

You can now use this LET form as such:

map(lambda x: LET(('y', x + 1,
                   'z', x - 1),
                  lambda o: o.y * o.z),
    [1, 2, 3])

which gives: [0, 3, 8]

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文