LondonRob
LondonRob

Reputation: 78863

Append to python parameter when type is unknown

I have Python v2.7.

I want to create a function which will accept None, a str or a list. I need to append something to the parameter so I've created a helper function as follows:

def _append_or_set(x, to_append):
    if x is None:
        return to_append
    if type(x) is str:
        return [x, to_append]
    if type(x) is list:
        x.append(to_append)
        return x

(Obviously this is not great programming, and there's no error handling etc. but it's just for illustration.)

But knowing Python, there is already a neat way of doing this. What is it?

Background on why I'm doing this

I'm doing this because I want to filter a dataset. The caller passes either None (i.e. return all the rows) or a string (just rows matching this value) or a list (rows matching any of these values)

Regardless of what the caller asks for, I always need to include rows containing foo at the very least. So I do:

def _filter_data(filter_by):
    filter_by = _append_or_set(filter_by, 'foo')
    return do_my_filtering(filter_by)

Upvotes: 0

Views: 685

Answers (3)

JAB
JAB

Reputation: 21089

Python 3.4 adds a singledispatch decorator to the functools module, which lets you use a bit more of a generic programming style:

@singledispatch
def append_or_set(x, to_append):
    raise RuntimeError("Unregistered type")

# This one isn't strictly necessary; you could just have the default behavior
# in the original function be to return the value to append if no other types
# will be supplied.
@append_or_set.register(type(None))
def append_or_set(x, to_append):
    return to_append


@append_or_set.register(str)
def append_or_set(x, to_append):
    return [x, to_append]

@append_or_set.register(list)
def append_or_set(x, to_append):
    x.append(to_append)
    return x

You can find more information in the docs, but the most relevant point is one not listed there: the implementation of functools.singledispatch is all Python, so even though you're using Python 2.7 you can backport the necessary code from Python 3.4's version of functools.py and use that. It shouldn't be too much trouble as the functions associated with singledispatch don't appear to use any functionality from Python 3 that is significantly different from that usable in 2.7 even without __future__ imports (though I may well be wrong on that as I haven't tested it).

Also, as others have said, it may be a good idea to make the return values more consistent and, unless desired, avoid mutating arguments, e.g. return to_append would become return [to_append] and x.append(to_append);return x would become return x + [to_append].

Upvotes: 0

poke
poke

Reputation: 388013

While your function would work fine (except that you should probably use isinstance and elifs), I do see a problem with it, and that is that it does not really have a consistent interface. Assuming that to_append is a string, there are three different situations:

  1. None is passed, the function returns a string.
  2. A string is passed, the function returns a 2-element list.
  3. A list is passed, the function appends to the list and returns it. The original passed list is also changed (meaning a possible side effect to the parameter).

Instead, you should try to keep the interface consistent. For example, always return a list and don’t touch the parameter itself:

def _append_or_set(x, to_append):
    if x is None:
        return [to_append]
    elif isinstance(x, (list, tuple, set)): # also accept tuples and sets
        return list(x) + [to_append]
    else:
        # assume that anything else is fine as the first element
        return [x, to_append]

Upvotes: 2

wnnmaw
wnnmaw

Reputation: 5534

Its possible that I still may not be understanding what you want, but I think this will work:

>>> def _append_or_set(x, to_append):
...     try:
...         x.append(to_append)
...     except AttributeError:
...         if x:
...             x = [x, to_append]
...         else:
...             x = to_append
...     finally:
...         return x
...
>>> _append_or_set([5,3,4], 6)
[5, 3, 4, 6]
>>> _append_or_set("this is x,", "this is appended")
['this is x,', 'this is appended']
>>> _append_or_set(None, "hello")
'hello'

By using try...except...finally you can avoid explicit type checking which is generally considered bad practice.

The first try assumes x is a list. It isn't (if x.append() breaks), then it must be a string, or None. A simple if will determine which it is.

Upvotes: 2

Related Questions