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

gev0vcfq  于 2023-02-07  发布在  Python
关注(0)|答案(8)|浏览(117)

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

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

我知道我可能会对第一个Map和接下来的两个过滤器使用列表解析,但我不确定如何进行最后一个Map以及如何将所有内容放在一起。
谢谢大家!

qcbq4gxm

qcbq4gxm1#

from functools import reduce

class my_list(list):
    def filter(self, func):
        return my_list(filter(func, self))
    def map(self, func):
        return my_list(map(func, self))
    def reduce(self, func):
        return reduce(func, self)

temp = my_list([1,2,3,4,5,6,7])
print(temp.filter(lambda n: n%2==0).map(lambda n: n*2).reduce(lambda a,b: a+b))

如果你想使用内置的filter,map和reduce方法,你可以在python中使用继承来实现同样的事情,这里我创建了一个名为my_list的类,它扩展了class list,我将用my_list Package 我的列表,然后通过传递一个函数作为参数来使用从我的类中定义的map,filter和reduce。
我知道每次调用这三个方法时都会创建一个新的对象。如果有任何方法可以绕过多个对象的创建,请让我知道。

yws3nbqq

yws3nbqq2#

如果您不介意使用软件包,那么使用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(transform)

如前所述,python允许你使用lambba表达式,但是它们不像javascript中的修饰符那样灵活,因为它们不能有一个以上的语句。另一个令人讨厌的python特性是需要反斜杠。话虽如此,我认为以上最接近你最初发布的内容。
免责声明:我是上述软件包的作者

7fyelxc5

7fyelxc53#

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

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)

注意,Map和过滤器的实际应用都是在最后两行完成的,前面的部分只是定义对Map和过滤器进行编码的函数。
第二个生成器解析同时完成最后两个过滤器和Map。使用生成器解析意味着inventory中的每个原始项都将被惰性地Map/过滤。它不会预处理整个列表,因此如果列表很大,它可能会执行得更好。
注意,没有Python中的等价物可以像JavaScript示例中那样内联定义long函数,不能内联指定复杂的filter(item.type == "cake"),而是必须将其定义为一个单独的函数,就像is_in_stockas_item一样。
(The第一个Map被拆分的原因是后面的过滤器在Map后才能作用于Map的数据。它可以合并成一个,但这需要在解析中手动重做as_itemMap:

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

把Map分离出来会更清楚。)

c6ubokkw

c6ubokkw4#

使用迭代器(在python3中所有这些函数都是迭代器,在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
xvw2m8pv

xvw2m8pv5#

定义你自己的函数组合元函数是非常容易的。一旦你有了它,把函数链接在一起也是非常容易的。

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/的东西很好)。

0h4hbjxa

0h4hbjxa6#

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

bjp0bcyl7#

我们可以使用Pyterator:https://github.com/remykarem/pyterator(免责声明:我是作者),和@EntilZha的图书馆很像。

pip install git+https://github.com/remykarem/pyterator#egg=pyterator

定义功能

def is_in_stock(x):
    pass

def as_item(x):
    pass

def transform_cake_noncake(item):
    pass

那么

from pyterator import iterate

low_weight_items = (
    iterate(shop.inventory)
    .map(as_item)
    .filter(is_in_stock)
    .filter(lambda item: item.weight < 1000)
    .map(transform_cake_noncake)
    .to_list()
)

注意,像mapfilter这样的所有操作都是延迟求值的,所以你需要调用.to_list()来求值管道,否则,它将保持为Iterator(你可以在以后的for循环中使用它)。
也类似于Fluentpy(https://github.com/dwt/fluent)。

0tdrvxhp

0tdrvxhp8#

您可以在生成器理解中使用walrus操作符来实现这一点。

low_weight_items = (
    z
    for x in [
        Item(1, 100, "cake"),
        Item(2, 1000, "cake"),
        Item(3, 900, "cake"),
        Item(4, 10000, "cake"),
        Item(5, 100, "bread"),
    ]
    if (y := as_item(x))
    if is_in_stock(y)
    if y.weight < 1000
    if (z := transform(y))
)

但是您必须分配不同的变量(示例中的x/y/z),因为您不能使用walrus操作符分配给现有变量。

完整示例

def as_item(x):
    return x

def is_in_stock(x):
    return True

class Item:
    def __init__(self, id, weight, type):
        self.id = id
        self.weight = weight
        self.type = type

class CatalogItem:
    def __init__(self, id, barcode):
        self.id = id
        self.barcode = barcode

def retrieve_catalog_item(id):
    return CatalogItem(id, "123456789")

def default_transformer(item):
    return item

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 = (
    z
    for x in [
        Item(1, 100, "cake"),
        Item(2, 1000, "cake"),
        Item(3, 900, "cake"),
        Item(4, 10000, "cake"),
        Item(5, 100, "bread"),
    ]
    if (y := as_item(x))
    if is_in_stock(y)
    if y.weight < 1000
    if (z := transform(y))
)

for item in low_weight_items:
    print(item)

相关问题