提问人:Paul 提问时间:7/5/2022 最后编辑:Paul 更新时间:7/5/2023 访问量:135
Python 中不可知地将 append() / add() 添加到集合(或其他接收器)的方法?
A way in Python to agnostically append() / add() to a collection (or other receiver)?
问:
在 Python 中是否有办法不可知地添加到集合中?
鉴于鸭子打字的盛行,我很惊讶添加到 a 的方法 is 但添加到 a 的方法 是 .list
append(x)
set
add(x)
我正在编写一系列需要构建集合的实用程序函数,理想情况下,我希望它们不关心什么类型正在积累结果。它至少应该适用于其他目标,理想情况下,只要他们知道要实现什么方法。从本质上讲,这里的鸭子类型是“可以添加项目的东西”。list
set
在实践中,这些实用程序函数要么传递给目标对象以将结果添加到其中,要么(更常见的是)在需要时生成目标类型的新实例的函数。
例如:
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。
答:
(这是一个经过编辑的答案。随意查看我的旧答案的历史,但它与问题无关)
方法是在这里使用标准库。您可以使用一些更普遍地处理可迭代对象的内置函数,而不是操作列表。如 itertools。pythonic
此功能是使用 的粗略指南。它不处理不为空的情况。它也有点密集,如果你不习惯 python,你可能不会发现它非常容易阅读,但从技术上讲,它可能是更 pythonic 的方法之一。我不确定我是否建议使用它,但它在相当少的行数中做了很多事情,这就是重点。不过,我相信其他人可以找到一种“更 pythonic”的方法。itertools
f_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
您的问题已被编辑,所以我现在将解决其中的明确要点:
如果你想要一种鸭子类型的方法来添加到可迭代对象中,你可能应该创建自己的数据结构。这样,您可以处理所有可迭代对象,而不仅仅是集合和列表。如果你传入一个生成器,或者已经发生或结果的东西,你可能也希望能够处理它们,对吧?sorted
map
下面是这类事情的包装器示例:
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。我认为应该由调用者来管理函数的输出。我个人认为,要求函数调用者只传入一个 是完全可以接受的,并且将其转换为集合的责任应该是分开的。这导致了更清晰、更简洁的函数,需要更少的逻辑。如果您期望集合和/或字典将成为一种常见的接受方法,那么可能值得单独处理。正如在对您的问题的评论中提到的,这些是根本不同的数据类型(尤其是没有排序的集合,因此如果不首先将其分类到非集合中,就无法真正整理)。set
set
MutableSequence
评论
List
Set
Collection
add(T x)
xs
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]]
这是我最终得到的解决方案......
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}]
评论
稍后再谈这个问题,我发现了一个更好的解决方案,用户使用它也可以扩展到其他类型。@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)
评论
MutableSet
并且是具有不同语义的不同 ABC,使用相同的方法没有意义。对于序列,您将在末尾放置一个新值,以及它们要么在内,要么不在集合中。此外,添加到集合中是幂等的,而附加到序列则不是。MutableSequence
theList.append
theSet.add
list
set
Collection.add()
add
add
append