32

是否有一个与 Haskell 'let' 表达式等效的 Python 表达式,可以让我编写如下内容:

list2 = [let (name,size)=lookup(productId) in (barcode(productId),metric(size)) 
            for productId in list]

如果不是,那么最易读的选择是什么?

添加以澄清 let 语法:

x = let (name,size)=lookup(productId) in (barcode(productId),metric(size))

相当于

(name,size) = lookup(productId)
x = (barcode(productId),metric(size))

但是,第二个版本在列表推导中效果不佳。

4

10 回答 10

24

您可以使用临时列表理解

[(barcode(productId), metric(size)) for name, size in [lookup(productId)]][0]

或者,等效地,生成器表达式

next((barcode(productId), metric(size)) for name, size in [lookup(productId)])

但这两者都非常可怕。

另一种(可怕的)方法是通过一个临时的 lambda,您可以立即调用它

(lambda (name, size): (barcode(productId), metric(size)))(lookup(productId))

我认为推荐的“Pythonic”方式只是定义一个函数,比如

def barcode_metric(productId):
   name, size = lookup(productId)
   return barcode(productId), metric(size)
list2 = [barcode_metric(productId) for productId in list]
于 2012-08-31T16:52:46.437 回答
20

最近的 python 版本允许在生成器表达式中使用多个 for 子句,因此您现在可以执行以下操作:

list2 = [ barcode(productID), metric(size)
          for productID in list
          for (name,size) in (lookup(productID),) ]

这也类似于 Haskell 提供的:

list2 = [ (barcode productID, metric size)
        | productID <- list
        , let (name,size) = lookup productID ]

并且在外延上等价于

list2 = [ (barcode productID, metric size) 
        | productID <- list
        , (name,size) <- [lookup productID] ]
于 2015-11-06T08:49:43.293 回答
12

哪有这回事。你可以let用与 lambda 演算 ( let x = foo in bar<=> )相同的方式来模拟它(\x -> bar) (foo)

最易读的替代方案取决于具体情况。对于您的具体示例,我会选择类似的东西[barcode(productId), metric(size) for productId, (_, size) in zip(productIds, map(lookup, productIds))](再想一想真的很难看,如果您也不需要它会更容易productId,那么您可以使用map)或显式for循环(在生成器中):

def barcodes_and_metrics(productIds):
    for productId in productIds:
        _, size = lookup(productId)
        yield barcode(productId), metric(size)
于 2012-08-31T16:51:05.103 回答
9

b0fh 的答案中的多个 for 子句是我个人使用了一段时间的样式,因为我相信它提供了更多的清晰度,并且不会用临时函数使命名空间混乱。但是,如果速度是一个问题,重要的是要记住临时构建一个元素列表比构建一个元组花费的时间要长得多。

比较这个线程中各种解决方案的速度,我发现丑陋的 lambda hack 最慢,其次是嵌套生成器,然后是 b0fh 的解决方案。然而,这些都被一元组获胜者超越:

list2 = [ barcode(productID), metric(size)
          for productID in list
          for (_, size) in (lookup(productID),) ]

这可能与 OP 的问题不太相关,但是在其他情况下,通过使用单元组而不是虚拟迭代器的列表,可以大大提高清晰度并提高速度。

于 2016-01-24T15:10:36.527 回答
4

只猜测 Haskell 做了什么,这是另一种选择。它使用 Python 中所谓的“列表理解”。

[barcode(productId), metric(size)
    for (productId, (name, size)) in [
        (productId, lookup(productId)) for productId in list_]
]

lambda:正如其他人所建议的那样,您可以包括使用。

于 2012-08-31T16:56:14.520 回答
4

由于您要求最好的可读性,您可以考虑使用 lambda 选项,但需要稍作改动:初始化参数。以下是我自己使用的各种选项,从我尝试过的第一个开始,到我现在最常用的一个结束。

假设我们有一个data_structure作为参数获取的函数(未显示),并且您需要x反复从中获取。

第一次尝试(根据 2012 年 huon 的回答):

(lambda x:
    x * x + 42 * x)
  (data_structure['a']['b'])

使用多个符号,这变得不那么可读,所以接下来我尝试了:

(lambda x, y:
    x * x + 42 * x + y)
  (x = data_structure['a']['b'],
   y = 16)

这仍然不是很可读,因为它重复了符号名称。所以我尝试了:

(lambda x = data_structure['a']['b'],
        y = 16:
  x * x + 42 * x + y)()

这几乎读作“让”表达。作业的定位和格式当然是你的。

这个成语很容易通过开头的“(”和结尾的“()”来识别。

在函数式表达式中(也在 Python 中),许多括号往往会在末尾堆积。奇怪的“(”很容易被发现。

于 2017-12-05T16:55:58.427 回答
4
class let:
    def __init__(self, var):
        self.x = var

    def __enter__(self):
        return self.x

    def __exit__(self, type, value, traceback):
        pass

with let(os.path) as p:
    print(p)

但这实际上与' 范围不限于 with 块p = os.path相同。p为此,您需要

class let:
    def __init__(self, var):
        self.value = var
    def __enter__(self):
        return self
    def __exit__(self, type, value, traceback):
        del var.value
        var.value = None

with let(os.path) as var:
    print(var.value)  # same as print(os.path)
print(var.value)  # same as print(None)

这里var.valueNone在 with 块之外,但os.path在其中。

于 2019-04-22T21:16:28.790 回答
4

在 Python 3.8 中,添加了使用运算符的赋值表达式:=PEP 572

这有点像let在 Haskell 中使用,尽管不支持可迭代解包。

list2 = [
    (lookup_result := lookup(productId), # store tuple since iterable unpacking isn't supported
     name := lookup_result[0], # manually unpack tuple
     size := lookup_result[1],
     (barcode(productId), metric(size)))[-1] # put result as the last item in the tuple, then extract on the result using the (...)[-1]
    for productId in list1
]

请注意,它的作用域类似于普通的 Python 赋值。

于 2021-02-05T15:28:20.707 回答
3

要获得模糊可比的东西,您要么需要做两个推导或映射,要么定义一个新函数。一种尚未被建议的方法是像这样将其分成两行。我相信这有点可读;虽然可能定义自己的函数是正确的方法:

pids_names_sizes = (pid, lookup(pid) for pid in list1)
list2 = [(barcode(pid), metric(size)) for pid, (name, size) in pids_names_sizes]
于 2012-08-31T17:14:49.497 回答
2

尽管您可以简单地将其写为:

list2 = [(barcode(pid), metric(lookup(pid)[1]))
         for pid in list]

您可以定义LET自己以获得:

list2 = [LET(('size', lookup(pid)[1]),
             lambda o: (barcode(pid), metric(o.size)))
         for pid in list]

甚至:

list2 = map(lambda pid: LET(('name_size', lookup(pid),
                             'size', lambda o: o.name_size[1]),
                            lambda o: (barcode(pid), metric(o.size))),
            list)

如下:

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)
于 2017-06-24T13:57:27.033 回答