【问题标题】:Is there a Python equivalent of the Haskell 'let'是否有与 Haskell 'let' 等效的 Python
【发布时间】:2012-08-26 12:21:19
【问题描述】:

是否有与 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))

不过,第二个版本不能很好地处理列表推导。

【问题讨论】:

标签: python haskell functional-programming let


【解决方案1】:

您可以使用临时列表理解

[(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]

【讨论】:

  • 我仍然无法理解为什么您的单项列表只包含对 lookup 的一次调用的结果。前两个示例不会生成ValueError吗?
  • @senderle 不,他们工作。它生成一个包含元组lookup 的列表作为单独的元素返回。然后它遍历该列表(因此外部循环也只运行一次)以解包元素并将它们提供给其他函数。
  • @delnan,啊,所以应该进入外部列表理解。我反其道而行之。好吧,无论如何,+1 的功能。
【解决方案2】:

最近的 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] ]

【讨论】:

  • 使用长度为 1 的元组,以便 for ... inlet .. in 相同......太棒了!
  • 我喜欢你的解决方案,但我没有使用像 (expr,) 这样的元组,而是使用像 [expr] 这样的单元素列表,因为它没有悬空的逗号。
【解决方案3】:

没有这样的事情。您可以模拟它,就像 let 脱糖到 lambda 演算 (let x = foo in bar (\x -&gt; 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)

【讨论】:

  • 我担心在 Python 代码中使用 lambda 表达式执行此操作可能会产生不希望的副作用,例如你的门被一群拿着火炬和干草叉的愤怒 Pythonistas 踢了进来。跨度>
  • @senderle 你是对的,现在修复它。列表理解变得越来越丑陋,它变得越正确。
  • 我选择了你的第二个建议。第三个在实际代码中有点矫枉过正,尽管从理论上讲我喜欢第一个,但 McCann 可能是正确的。谢谢。
  • @Perseids 老实说,我认为第二行几乎和第一行一样糟糕。它很聪明,而且是单行的,但它不可读或不清晰。第三个意味着更多的行,但如果在多个地方使用它仍然很简洁,而且非常明显。
  • 在罗马时,像罗马人那样做。在大多数编程语言中违背预期的风格和习语是不明智的,在 Python 中更是如此(可选的妙语:...但在 perl 中,这是一种艺术形式)。
【解决方案4】:

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

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

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

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

【讨论】:

    【解决方案5】:

    只是猜测 Haskell 做了什么,这里有替代方案。它使用 Python 中所谓的“列表理解”。

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

    您可以按照其他人的建议包括使用 lambda:

    【讨论】:

    • 顺便说一句——如果我没记错的话,Python 中的列表推导很大程度上受到了 Haskell 的启发。除了语法细节和奇怪的 Haskell 编译器扩展外,它们非常相似。 Haskell 使用语法空白的时间也更长。 ;]
    • 你是说 python 正在用 Haskell 做 Java 对 Lisp 应该做的事情(或许也做了)——也就是说,将好东西从函数领域带入对象领域? :)
    【解决方案6】:

    由于您要求最好的可读性,您可以考虑使用 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 中也是如此),许多括号往往会在末尾堆积。奇怪的 '(' 很容易被发现。

    【讨论】:

    • 我非常喜欢你的第三个版本。正如你所说,它甚至看起来像 let 表达式!
    【解决方案7】:
    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)
    

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

    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.value 将是None 在with 块之外,但os.path 在其中。

    【讨论】:

      【解决方案8】:

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

      这有点像 Haskell 中的let,虽然不支持可迭代解包。

      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 赋值。

      【讨论】:

        【解决方案9】:

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

        pids_names_sizes = (pid, lookup(pid) for pid in list1)
        list2 = [(barcode(pid), metric(size)) for pid, (name, size) in pids_names_sizes]
        

        【讨论】:

          【解决方案10】:

          虽然你可以简单地写成:

          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)
          

          【讨论】:

            猜你喜欢
            • 2013-02-04
            • 2012-11-21
            • 1970-01-01
            • 2013-06-07
            • 2011-07-14
            • 2010-12-19
            • 2011-02-18
            • 2015-07-28
            • 1970-01-01
            相关资源
            最近更新 更多