簡體   English   中英

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

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

是否有一個 Python 等價於 Haskell 'let' 表達式,它可以讓我寫一些類似的東西:

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

不過,第二個版本在列表推導式中效果不佳。

您可以使用臨時列表理解

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

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

哪有這回事。 可以用同樣的方式模擬它, 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)

b0fh 的答案中的多個 for 子句是我個人已經使用了一段時間的樣式,因為我相信它提供了更多的清晰度並且不會用臨時函數混淆命名空間。 然而,如果速度是一個問題,重要的是要記住臨時構建一個單元素列表比構建一個單元組花費的時間要長得多。

比較這個線程中各種解決方案的速度,我發現丑陋的 lambda hack 最慢,其次是嵌套生成器,然后是 b0fh 的解決方案。 然而,這些都被一元組冠軍超越了:

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

這可能與 OP 的問題不太相關,但在其他情況下,通過使用單元組而不是虛擬迭代器的列表,可以大大提高清晰度並提高速度,在人們可能希望使用列表理解的情況下。

只猜測 Haskell 做了什么,這是替代方案。 它使用 Python 中已知的“列表理解”。

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

您可以使用lambda: ,正如其他人所建議的那樣。

由於您要求最佳可讀性,您可以考慮使用 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 中也是如此),許多括號往往會堆積在末尾。 奇怪的 '(' 很容易被發現。

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在 with 塊之外將是None ,但在其中的os.path

為了得到一些模糊的可比性,你要么需要做兩個推導式或映射,要么定義一個新函數。 尚未建議的一種方法是將其分成兩行,如下所示。 我相信這有點可讀; 雖然可能定義自己的函數是正確的方法:

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

盡管您可以簡單地將其寫為:

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)

在 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 賦值一樣。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM