Search code examples
pythonpython-3.8duck-typing

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


Is there a way in Python to add agnostically to a collection?

Given the prevalence of duck typing I was surprised that the method to add to a list is append(x) but the method to add to a set is add(x).

I'm writing a family of utility functions that need to build up collections and would ideally like them not to care what type is accumulating the result. It should at least work for list and set - and ideally for other targets, as long as they know what method to implement. Essentially, the duck type here is 'thing to which items can be added'.

In practice, these utility functions will either be passed the target object to add the results to, or - more commonly - a function that generates new instances of the target type when needed.

For example:

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'

Here collate() is just a simple example. I expect there's already a way to achieve this 'collation' in Python. That's not the real question here.

I'm currently using Python 3.8.5.


Solution

  • Returning to this later I found a better solution using @functools.singledispatch which is also user-extensible to additional types.

    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)