1

I often find myself in a situation where I have a variable that may or may not be a list of items that I want to iterate over. If it's not a list I'll make a list out of it so I can still iterate over it. That way I don't have to write the code inside the loop twice.

def dispatch(stuff):
   if type(stuff) is list:
       stuff = [stuff]

   for item in stuff:
      # this could be several lines of code
      do_something_with(item) 

What I don't like about this is (1) the two extra lines (2) the type checking which is generally discouraged (3) besides I really should be checking if stuff is an iterable because it could as well be a tuple, but then it gets even more complicated. The point is, any robust solution I can think of involves an unpleasant amount of boilerplate code.

You cannot ensure stuff is a list by writing for item in [stuff] because it will make a nested list if stuff is already a list, and not iterate over the items in stuff. And you can't do for item in list(stuff) either because the constructor of list throws an error if stuff is not an iterable.

So the question I'd like to ask: is there anything obvious I've missed to the effect of ensurelist(stuff), and if not, can you think of a reason why such functionality is not made easily accessible by the language?

Edit: In particular, I wonder why list(x) doesn't allow x to be non-iterable, simply returning a list with x as a single item.

Glemi
  • 676
  • 1
  • 7
  • 17
  • 1
    `"You cannot ensure stuff is a list by writing for item in [stuff] because it will make a nested list if stuff is already a list"` which is exactly what your current `if` condition is doing as well. Did you mean `if not isinstance(stuff, list)` ? – DeepSpace Feb 23 '20 at 18:35
  • Why do you consider `if not isinstance(e, Iterable):` "complicated" or "boilerplate" ? The only caveat I see is that you need to remember that `str` is also `Iterable` – DeepSpace Feb 23 '20 at 18:38
  • https://docs.python.org/3/glossary.html#term-eafp -`try:` iterating and catch the error if it's not iterable. – jonrsharpe Feb 23 '20 at 18:45
  • 1
    Type checking is indeed generally discouraged, but having an `ensurelist` function wouldn’t really *not* be type checking. Instead, I’d recommend looking at why you find yourself in this situation and whether there’s a way to have a better-typed API. For example, the caller taking on the responsibility of providing the right type with `dispatch([stuff])`. (Equivalently, `def dispatch(*stuff):`, putting responsibility on callers that would like to pass iterables.) – Ry- Feb 23 '20 at 18:47
  • @Ry- this only shifts the problem to the calling code. if `stuff` is already a list then `dispatch` is called with a nested list – DeepSpace Feb 23 '20 at 18:49
  • @DeepSpace As explained in the question, I'd prefer not having to wrote two extra lines of code for this. Testing for ```Iterable``` is a good idea, but it's requires you to import from ```collections.abc``` which introduces another extra line. It just seems like a lot of code for something so trivial that should be doable with zero extra lines. – Glemi Feb 23 '20 at 18:50
  • @DeepSpace: Yes, I’m saying the caller should know what type it has. – Ry- Feb 23 '20 at 18:50
  • @Glemi How many lines your entire code base has? 100? 300? are you **really** that worried about 2-3 extra lines? sounds like an extreme micro/premature optimization – DeepSpace Feb 23 '20 at 18:52
  • Would someone care to explain the downvote? No research effort? Unclear? Not useful? – Glemi Feb 23 '20 at 18:52
  • I didn’t downvote, but I do think doing this is never a good idea. (DeepSpace already mentioned `str`, which is a common practical issue even for code whose cleanliness you don’t really care about.) – Ry- Feb 23 '20 at 19:04
  • Sure @DeepSpace it could be considered micro-optimization, but I still thought it was an interesting enough question to ask. Btw @Ry-♦ I *really* like the ```def dispatch(*stuff)``` suggestion, thanks for that. – Glemi Feb 23 '20 at 19:12

1 Answers1

0

Consider the example of the classes defined in the io module, which provide separate write and writelines methods for writing a single line and writing a list of lines. Provide separate functions that do different things. (One can even use the other.)

def dispatch(stuff):
   do_something_with(item)


def dispatch_all(stuff):
    for item in stuff:
        dispatch(item)

The caller will have an easier time deciding whether dispatch or dispatch_all is the correct function to use than your do-it-all function will have deciding whether it needs to iterate over its argument or not.

chepner
  • 497,756
  • 71
  • 530
  • 681