链接地图和过滤器的Python方式是什么?

时间:2014-07-18 18:23:00

标签: python

我目前正在学习Python(来自其他语言,如JavaScript和Ruby)。我非常习惯链接一堆转换/过滤器​​,但我很确定在Python中不是正确的方法:filter在可枚举之前需要一个lambda,所以写长/多行函数看起来很奇怪,链接它们意味着将它们放在相反的顺序中,这是不可读的。

" Python方式"在这个JavaScript函数中编写地图和过滤器?

let is_in_stock = function() /* ... */
let as_item = function() /* ... */

let low_weight_items = shop.inventory
    .map(as_item)
    .filter(is_in_stock)
    .filter(item => item.weight < 1000)
    .map(item => {
        if (item.type == "cake") {
            let catalog_item = retrieve_catalog_item(item.id);

            return {
                id: item.id,
                weight: item.weight,
                barcode: catalog_item.barcode
            };
        } else {
            return default_transformer(item);
        }
    });

我知道我可能会对第一张地图和接下来的两张过滤器使用列表理解,但我不知道如何制作最后一张地图以及如何将所有内容放在一起。

谢谢!

5 个答案:

答案 0 :(得分:12)

如果您不介意使用软件包,这是使用https://github.com/EntilZha/PyFunctional

执行此操作的另一种方法
from functional import seq

def as_item(x):
    # Implementation here
    return x

def is_in_stock(x):
    # Implementation
    return True

def transform(item):
    if item.type == "cake":
        catalog_item = retrieve_catalog_item(item.id);
        return {
            'id': item.id,
            'weight': item.weight,
            'barcode': catalog_item.barcode
        }
    else:
        return default_transformer(item)

low_weight_items = seq(inventory)\
    .map(as_item)\
    .filter(is_in_stock)\
    .filter(lambda item: item.weight < 1000)\
    .map(transformer)

如前所述,python允许你使用lamdba表达式,但它们不像javascript中的clojures那样灵活,因为它们不能有多个语句。另一个恼人的python事情是需要反斜杠。话虽如此,我认为上述内容最接近您最初发布的内容。

免责声明:我是上述套餐的作者

答案 1 :(得分:8)

这样做的一个好方法是将多个过滤器/贴图组合成单个生成器理解。如果无法做到这一点,请为您需要的中间映射/过滤器定义一个中间变量,而不是试图将映射强制为单个链。例如:

def is_in_stock(x):
   # ...
def as_item(x):
   # ...
def transform(item):
    if item.type == "cake":
        catalog_item = retrieve_catalog_item(item.id)
        return {
            "id": item.id,
            "weight": item.weight,
            "barcode": catalog_item.barcode
        }
    else:
        return default_transformer(item)

items = (as_item(item) for item in shop.inventory)
low_weight_items = (transform(item) for item in items if is_in_stock(item) and item.weight < 1000)

请注意,地图和过滤器的实际应用都是在最后两行中完成的。前面的部分只是定义了编码地图和过滤器的函数。

第二个生成器理解将最后两个过滤器和地图放在一起。使用生成器理解意味着inventory中的每个原始项目将被懒惰地映射/过滤。它不会预处理整个列表,因此如果列表很大,它可能会表现得更好。

请注意,没有Python等同于在JavaScript示例中定义内联长函数。您无法指定内联的复杂过滤器(具有item.type == "cake"的过滤器)。相反,如我的示例所示,必须将其定义为单独的函数,就像使用is_in_stockas_item一样。

(第一个地图被拆分的原因是后来的过滤器在映射之前不能对映射数据起作用。它可以合并为一个,但这需要手动重做内部的as_item映射。理解:

low_weight_items = (transform(as_item(item)) for item in items if is_in_stock(as_item(item)) and as_item(item).weight < 1000)

将该地图分开是更清楚的。)

答案 2 :(得分:3)

使用迭代器(在python 3中,所有这些函数都是python2中的迭代器,你需要使用itertools.imap和itertools.ifilter)

m = itertools.imap
f = itertools.ifilter
def final_map_fn(item):
   if (item.type == "cake"):
        catalog_item = retrieve_catalog_item(item.id);
        return {
            "id": item.id,
            "weight": item.weight,
            "barcode": catalog_item.barcode}
    else:
        return default_transformer(item)

items = m(as_item,shop.inventory) #note it does not loop it yet
instockitems = f(is_in_stock,items) #still hasnt actually looped anything
weighteditems = (item for item instockitems if item.weight < 100) #still no loop (this is a generator)
final_items = m(final_map_fn,weighteditems) #still has not looped over a single item in the list
results = list(final_items) #evaluated now with a single loop

答案 3 :(得分:2)

定义自己的功能组合元功能非常简单。一旦掌握了这些功能,将功能链接在一起也非常容易。

import functools
def compose(*functions):
    return functools.reduce(lambda f, g: lambda x: f(g(x)), functions)
def make_filter(filter_fn):
    return lambda iterable: (it for it in iterable if filter_fn(it))

pipeline = compose(as_item, make_filter(is_in_stock),
                   make_filter(lambda item: item.weight < 1000),
                   lambda item: ({'id': item.id,
                                 'weight': item.weight,
                                 'barcode': item.barcode} if item.type == "cake"
                                 else default_transformer(item)))
pipeline(shop.inventory)

如果您还不熟悉迭代器,如果我是你(如http://excess.org/article/2013/02/itergen1/那样好的话),我会刷新它。

答案 4 :(得分:0)

def is_in_stock():
    ...

def as_item():
    ...

def get_low_weight_items(items):
    for item in items:
        item = as_item(item)
        if not is_in_stock(item):
            continue
        if item.weight < 1000:
            if item.type == "cake":
                catalog_item = retrieve_catalog_item(item.id)
                yield {
                    "id": item.id,
                    "weight": item.weight,
                    "barcode": catalog_item.barcode,
                }
            else:
                yield default_transformer(item)


low_weight_items = list(get_low_weight_items(items))