有没有相当于Haskell'let'的Python

问题描述 投票:19回答:9

是否有一个类似于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
9个回答
18
投票

您可以使用临时列表理解

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

10
投票

哪有这回事。你可以用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) 循环,这样就更容易了。

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

10
投票

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

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

这与Haskell提供的类似:

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

和指称相当于

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

6
投票

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

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

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

这可能与OP的问题没那么相关,但是在其他情况下,通过使用单元组而不是虚拟迭代器的列表,可以大大增强清晰度并且在人们可能希望使用列表解析的情况下获得速度。


3
投票

为了得到一些模糊可比的东西,你要么需要做两个理解或地图,要么定义一个新的功能。尚未提出的一种方法是将其分解为两行。我相信这有点可读;虽然可能定义自己的功能是正确的方法:

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

2
投票

只是猜测Haskell的作用,这里是替代方案。它使用Python中已知的“列表理解”。

lambda:

您可以像其他人所建议的那样使用data_structure


2
投票

既然你要求最好的可读性,你可以考虑使用lambda-option,但是要小一点:初始化参数。以下是我自己使用的各种选项,从我尝试的第一个开始,到现在最常用的那个开头。

假设我们有一个函数(未显示)将x作为参数,你需要反复从中获取(lambda x: x * x + 42 * x) (data_structure['a']['b'])

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

(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)()

这仍然不是很可读,因为它重复了符号名称。那么我试过:

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

这几乎就像一个'let'表达式。当然,作业的定位和格式是你的。

这个成语很容易通过起始'('和结尾'()'来识别。

在函数表达式中(也在Python中),许多括号倾向于在最后堆积。奇怪的一个'('很容易被发现。


1
投票

虽然您可以简单地将其写为:

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)

如下:

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)

1
投票
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) 的范围并不局限于with block。要达到这个目标,你需要

var.value

在这里,None将是os.path以外的区块,但qazxswpoi在其中。

© www.soinside.com 2019 - 2024. All rights reserved.