简体   繁体   English

是否有与 Haskell 'let' 等效的 Python

[英]Is there a Python equivalent of the Haskell 'let'

Is there a Python equivalent of the Haskell 'let' expression that would allow me to write something like:是否有一个 Python 等价于 Haskell 'let' 表达式,它可以让我写一些类似的东西:

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

If not, what would be the most readable alternative?如果没有,最易读的替代方案是什么?

Added for clarification of the let syntax:添加以澄清 let 语法:

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

is equivalent to相当于

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

The second version doesn't work that well with list comprehensions, though.不过,第二个版本在列表推导式中效果不佳。

You could use a temporary list comprehension您可以使用临时列表理解

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

or, equivalently, a generator expression或者,等效地,生成器表达式

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

but both of those are pretty horrible.但这两个都非常可怕。

Another (horrible) method is via a temporary lambda, which you call immediately另一个(可怕的)方法是通过一个临时的 lambda,您可以立即调用它

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

I think the recommended "Pythonic" way would just be to define a function, like我认为推荐的“Pythonic”方式就是定义一个函数,比如

def barcode_metric(productId):
   name, size = lookup(productId)
   return barcode(productId), metric(size)
list2 = [barcode_metric(productId) for productId in list]

Recent python versions allows multiple for clauses in a generator expression, so you can now do something like:最近的 Python 版本允许在生成器表达式中使用多个 for 子句,因此您现在可以执行以下操作:

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

which is similar to what Haskell provides too:这也类似于 Haskell 提供的内容:

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

and denotationally equivalent to并且在外延上等同于

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

There is no such thing.哪有这回事。 You could emulate it the same way let is desugared to lambda calculus ( let x = foo in bar <=> (\\x -> bar) (foo) ).可以用同样的方式模拟它, let脱糖为 lambda 演算( let x = foo in bar <=> (\\x -> bar) (foo) )。

The most readable alternative depends on the circumstances.最易读的选择取决于具体情况。 For your specific example, I'd choose something like [barcode(productId), metric(size) for productId, (_, size) in zip(productIds, map(lookup, productIds))] (really ugly on second thought, it's easier if you don't need productId too, then you could use map ) or an explicit for loop (in a generator):对于您的具体示例,我会选择类似[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)

The multiple for clauses in b0fh's answer is the style I have personally been using for a while now, as I believe it provides more clarity and doesn't clutter the namespace with temporary functions. b0fh 的答案中的多个 for 子句是我个人已经使用了一段时间的样式,因为我相信它提供了更多的清晰度并且不会用临时函数混淆命名空间。 However, if speed is an issue, it is important to remember that temporarily constructing a one element list takes notably longer than constructing a one-tuple.然而,如果速度是一个问题,重要的是要记住临时构建一个单元素列表比构建一个单元组花费的时间要长得多。

Comparing the speed of the various solutions in this thread, I found that the ugly lambda hack is slowest, followed by the nested generators and then the solution by b0fh.比较这个线程中各种解决方案的速度,我发现丑陋的 lambda hack 最慢,其次是嵌套生成器,然后是 b0fh 的解决方案。 However, these were all surpassed by the one-tuple winner:然而,这些都被一元组冠军超越了:

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

This may not be so relevant to the OP's question, but there are other cases where clarity can be greatly enhanced and speed gained in cases where one might wish to use a list comprehension, by using one-tuples instead of lists for dummy iterators.这可能与 OP 的问题不太相关,但在其他情况下,通过使用单元组而不是虚拟迭代器的列表,可以大大提高清晰度并提高速度,在人们可能希望使用列表理解的情况下。

Only guessing at what Haskell does, here's the alternative.只猜测 Haskell 做了什么,这是替代方案。 It uses what's known in Python as "list comprehension".它使用 Python 中已知的“列表理解”。

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

You could include the use of lambda: , as others have suggested.您可以使用lambda: ,正如其他人所建议的那样。

Since you asked for best readability you could consider the lambda-option but with a small twist: initialise the arguments.由于您要求最佳可读性,您可以考虑使用 lambda 选项,但有一个小改动:初始化参数。 Here are various options I use myself, starting with the first I tried and ending with the one I use most now.以下是我自己使用的各种选项,从我尝试的第一个开始,到我现在最常用的一个结束。

Suppose we have a function (not shown) which gets data_structure as argument, and you need to get x from it repeatedly.假设我们有一个函数(未显示),它以data_structure作为参数,并且您需要重复从中获取x

First try (as per 2012 answer from huon):第一次尝试(根据 2012 年 huon 的回答):

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

With multiple symbols this becomes less readable, so next I tried:使用多个符号时,这会变得不那么可读,所以接下来我尝试了:

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

That is still not very readable as it repeats the symbolic names.这仍然不是很可读,因为它重复了符号名称。 So then I tried:然后我尝试了:

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

This almost reads as an 'let' expression.这几乎读作“让”表达式。 The positioning and formatting of the assignments is yours of course.作业的定位和格式当然是你的。

This idiom is easily recognised by the starting '(' and the ending '()'.这个习语很容易通过开头的 '(' 和结尾的 '()' 识别。

In functional expressions (also in Python), many parenthesis tend to pile up at the end.在函数表达式中(在 Python 中也是如此),许多括号往往会堆积在末尾。 The odd one out '(' is easily spotted.奇怪的 '(' 很容易被发现。

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)

But this is effectively the same as p = os.path as p 's scope is not confined to the with block.但这实际上与p = os.path相同,因为p的范围不限于 with 块。 To achieve that, you'd need要实现这一点,你需要

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)

Here var.value will be None outside of the with block, but os.path within it.这里var.value在 with 块之外将是None ,但在其中的os.path

To get something vaguely comparable, you'll either need to do two comprehensions or maps, or define a new function.为了得到一些模糊的可比性,你要么需要做两个推导式或映射,要么定义一个新函数。 One approach that hasn't been suggested yet is to break it up into two lines like so.尚未建议的一种方法是将其分成两行,如下所示。 I believe this is somewhat readable;我相信这有点可读; though probably defining your own function is the right way to go:虽然可能定义自己的函数是正确的方法:

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

Although you can simply write this as:尽管您可以简单地将其写为:

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

You could define LET yourself to get:您可以自己定义LET以获得:

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

or even:甚至:

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

as follows:如下:

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)

In Python 3.8, assignment expressions using the := operator were added: PEP 572 .在 Python 3.8 中,添加了使用:=运算符的赋值表达式: PEP 572

This can be used somewhat like let in Haskell, although iterable unpacking is not supported.这可以像 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
]

Note that this is scoped like a normal Python assignment.请注意,这与普通的 Python 赋值一样。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM