Python 中不可知地将 append() / add() 添加到集合(或其他接收器)的方法?

A way in Python to agnostically append() / add() to a collection (or other receiver)?

提问人:Paul 提问时间:7/5/2022 最后编辑:Paul 更新时间:7/5/2023 访问量:135

问:

在 Python 中是否有办法不可知地添加到集合中?

鉴于鸭子打字的盛行,我很惊讶添加到 a 的方法 is 但添加到 a 的方法 是 .listappend(x)setadd(x)

我正在编写一系列需要构建集合的实用程序函数,理想情况下,我希望它们不关心什么类型正在积累结果。它至少应该适用于其他目标,理想情况下,只要他们知道要实现什么方法。从本质上讲,这里的鸭子类型是“可以添加项目的东西”。listset

在实践中,这些实用程序函数要么传递给目标对象以将结果添加到其中,要么(更常见的是)在需要时生成目标类型的新实例的函数。

例如:

def collate(xs, n, f_make=lambda: list()):
    if n < 1:
        raise ValueError('n < 1')
    col = f_make()
    for x in xs:
        if len(col) == n:
            yield col
            col = f_make()
        col.append(x)  # append() okay for list but not for set
    yield col
>>> list(collate(range(6), 3))
[[0, 1, 2], [3, 4, 5]]

>>> list(collate(range(6), 4))
[[0, 1, 2, 3], [4, 5]]

>>> # desired result here: [{0, 1, 2, 3}, {4, 5}]
>>> list(collate(range(6), 4, f_make=lambda: set()))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/paul/proj/mbrain/src/fossil/fn.py", line 42, in collate
    col.append(x)
AttributeError: 'set' object has no attribute 'append'

这里只是一个简单的例子。我希望已经有一种方法可以在 Python 中实现这种“排序规则”。这不是真正的问题。collate()

我目前正在使用 Python 3.8.5。

python-3.8 鸭子类型

评论

2赞 jonrsharpe 7/5/2022
MutableSet并且是具有不同语义的不同 ABC,使用相同的方法没有意义。对于序列,您将在末尾放置一个新值,以及它们要么在内,要么不在集合中。此外,添加到集合中是幂等的,而附加到序列则不是。MutableSequence
1赞 jasonharper 7/5/2022
让函数采用调用该参数来添加项的回调参数。传递它或 .theList.appendtheSet.add
1赞 Paul 7/5/2022
@jonrsharpe 是的,明白了。尽管如此,恕我直言,具有累积元素特征的类型的概念并没有错:无论是 a 、 a 、某种队列,甚至是在接收时输出每个项目的东西。在 Java 中,您可以通过 .如果这不是 Python 中提供的抽象,那很好 - 问题就变成了最好的解决方法是什么。listsetCollection.add()
2赞 jonrsharpe 7/5/2022
或者,您可以创建一个描述“-able”的协议,并使用任何实现它的协议,例如,围绕代理到的列表的集合和瘦包装器。“最佳解决方法”将在很大程度上取决于上下文。addaddappend
2赞 CrazyChucky 7/6/2022
@Paul 你能把你的解决方案作为答案而不是你的问题发布吗?

答:

0赞 smcjones 7/5/2022 #1

(这是一个经过编辑的答案。随意查看我的旧答案的历史,但它与问题无关)

方法是在这里使用标准库。您可以使用一些更普遍地处理可迭代对象的内置函数,而不是操作列表。如 itertoolspythonic

此功能是使用 的粗略指南。它不处理不为空的情况。它也有点密集,如果你不习惯 python,你可能不会发现它非常容易阅读,但从技术上讲,它可能是更 pythonic 的方法之一。我不确定我是否建议使用它,但它在相当少的行数中做了很多事情,这就是重点。不过,我相信其他人可以找到一种“更 pythonic”的方法。itertoolsf_make()

def collate(xs, n, f_make=list):
    result = f_make()
    for _, val in itertools.groupby(
        enumerate(xs),
        lambda i: (len(result) + i[0]) // n
    ):
        yield list(itertools.chain(result, (v[1] for v in val)))

编辑 2

您的问题已被编辑,所以我现在将解决其中的明确要点:

如果你想要一种鸭子类型的方法来添加到可迭代对象中,你可能应该创建自己的数据结构。这样,您可以处理所有可迭代对象,而不仅仅是集合和列表。如果你传入一个生成器,或者已经发生或结果的东西,你可能也希望能够处理它们,对吧?sortedmap

下面是这类事情的包装器示例:

class Appender:
  def __init__(self, iterable):
    self.length = sum(1 for _ in iterable)
    self.iterable = iter(iterable)

  def append(self, new_item):
    self.length += 1
    self.iterable = itertools.chain(self.iterable, new_item)

  def __iter__(self):
    return self.iterable

  def __len__(self):
    return self.length

请注意,您可以进一步将其修改为 MutableSequence,但我认为这对于您的用例来说并不是绝对必要的,因为您只需要长度。如果你不关心可迭代对象,那么我建议你更改你的问题标题以删除“或其他接收器”

另请注意,这不像 s(显然)那样处理 s。我认为应该由调用者来管理函数的输出。我个人认为,要求函数调用者只传入一个 是完全可以接受的,并且将其转换为集合的责任应该是分开的。这导致了更清晰、更简洁的函数,需要更少的逻辑。如果您期望集合和/或字典将成为一种常见的接受方法,那么可能值得单独处理。正如在对您的问题的评论中提到的,这些是根本不同的数据类型(尤其是没有排序的集合,因此如果不首先将其分类到非集合中,就无法真正整理)。setsetMutableSequence

评论

0赞 Paul 7/5/2022
我想说的是,实现“积累价值的事物”概念的 Python 方式是什么?在 Java 中 - 虽然没有“累加器”(或类似)的概念 - 我至少可以将(可变的)s 和 s 视为具有 .旁白:我不确定是什么让你认为它可以无限循环——但很想。ListSetCollectionadd(T x)
0赞 smcjones 7/6/2022
我只是运行了它,只要不是无限循环,就没有无限循环。我的错误 - 老实说没有给予它很多关注。对于一个一般性问题“如何同时累积集合和列表”,这是一个很难的例子?我正在编辑我的答案来回答您的一般问题。xs
0赞 smcjones 7/7/2022
我更新了问题以反映“pythonic”方式,这可能是在不操作集合的情况下做到这一点:)
0赞 Paul 7/7/2022
我发现恕我直言,该版本比我添加到问题中的解决方案更难阅读(随后从问题中删除)。它也不会产生相同的结果,例如 应该是而不是.同样,应该返回,但实际上返回上述内容。list(collate(range(6), 2))[[0, 1], [2, 3], [4, 5]][[0, 1], [2, 3, 4, 5]]list(collate(range(6), 1, set))[{0}, {1}, {2}, {3}, {4}, {5}][[0], [1, 2, 3, 4, 5]]
1赞 Paul 7/7/2022
可能值得注意的是,“整理”的细节并不是问题的真正内容;这是关于如何(至少)列出并设置不可知论者。
-1赞 Paul 7/7/2022 #2

这是我最终得到的解决方案......

def appender(xs):
    if isinstance(xs, MutableSequence):
        f = xs.append
    elif isinstance(xs, MutableSet):
        f = xs.add
    # Could probably do better validation here...
    elif hasattr(xs, 'append'):  
        f = getattr(xs, 'append')
    else:
        raise ValueError('Don\'t know how to append to ' + str(type(xs)))
    return f


def collate(xs, n, f_make=lambda: list()):
    if n < 1:
        raise ValueError('n < 1')
    col = f_make()
    app = appender(col)
    for x in xs:
        if len(col) == n:
            yield col
            col = f_make()
            app = appender(col)
        app(x)
    if col:
        yield col

>>> list(collate(range(6), 4, set))
[{0, 1, 2, 3}, {4, 5}]

>>> list(collate(range(6), 4, list))
[[0, 1, 2, 3], [4, 5]]

(我之前将其添加到问题中 - 它被删除了。所以我现在把它添加为一个答案。

此外,为了澄清预期行为:

>>> list(collate(range(6), 2, list))
[[0, 1], [2, 3], [4, 5]]

>>> list(collate(range(6), 1, set))
[{0}, {1}, {2}, {3}, {4}, {5}]

评论

0赞 smcjones 7/8/2022
它是什么 - 你是否关心函数的预期行为,或者你是否关心找到一种不可知的方式来添加到列表/集合,或者你是否关心一种不可知的方式来添加到任何可迭代对象,或者你关心一种 pythonic 的方式来做到这一点?请修改您的问题以澄清。我投票决定关闭ATM,这是基于混合消息和多个问题。
0赞 smcjones 7/8/2022
另外 - 这根本不是 pythonic(尽管这至少有点主观,但我认为没有人会投票认为这是 pythonic,所以我建议你也从你的问题中删除该要求)
0赞 Paul 7/8/2022
你是对的。我不应该说 Pythonic,而只是“Python 中有办法吗”。我应该注意的是,我只用 Python 进行了几周的广泛编码。到目前为止,我还没有看到任何明确的迹象表明“Pythonic”有任何非常明确的含义。我希望这将及时具体化。
0赞 Paul 8/30/2022 #3

稍后再谈这个问题,我发现了一个更好的解决方案,用户使用它也可以扩展到其他类型。@functools.singledispatch

import functools

@functools.singledispatch
def append(xs, v):
    raise ValueError('append() not supported for ' + str(type(xs)))


@append.register
def _(xs: MutableSequence, v):
    xs.append(v)


@append.register
def _(xs: MutableSet, v):
    xs.add(v)