2

I want to iterate over a variable which can either be a list or a string. Problem is, I don't want to consider the string as a list of characters:

[1, 2, 3] => [1, 2, 3]
["1", "2"] => ["1", "2"]
"123" => ["123"] # instead of ["1", "2", "3"]

for x in foo works for lists but iterates over characters assuming that foo is a string.

I know this can be done by checking the type (e.g. for x in (foo if type(foo) is list else [foo])) but I get a feeling there must be a better way ...

Ofir
  • 1,565
  • 3
  • 23
  • 41
  • I don't think there is a better way but that's a bad way to check types – jamylak May 30 '13 at 07:22
  • Although it's a different discussion - why is it a bad way to check types? – Ofir May 30 '13 at 07:25
  • It is not bad per se. Just a sign that your code probably needs refactoring. Just one of those things that trigger a warning flag. – Daren Thomas May 30 '13 at 07:27
  • It's not a different discussion, see here: http://stackoverflow.com/a/1549854/1219006 for a perfect answer from the very credible Alex Martelli – jamylak May 30 '13 at 07:27
  • 1
    What *is* bad, is having different behaviour based on the *type* of the arguments (obtained from checking the type). This is just confusing and sooo 1990... – Daren Thomas May 30 '13 at 07:29
  • Thanks jamylak. I guess for this specific question it doesn't matter but as a convention, it is a better practice ... – Ofir May 30 '13 at 08:08
  • Having lots of functions that vary their behaviour by type is bad. But switching on the data type when you are trying to serialize an internal data structure is just fine and to be expected. – poolie May 30 '13 at 08:10
  • @jamylak `if foo is list` is just plain wrong of course - it checks whether `foo` is a reference to the list type, not whether `foo` is *a* list. – poolie May 30 '13 at 08:13
  • @poolie I assumed that was a typo – jamylak May 30 '13 at 08:15
  • I thought maybe it was pseudocode. – poolie May 30 '13 at 08:20
  • @jamylak you are right, it is a typo. There's a missing type() there ... – Ofir May 30 '13 at 12:23

2 Answers2

6

The straightforward way is indeed

for x in ([foo] if isinstance(foo, basestring) else foo)

Why did I say isinstance(foo, basestring) rather than say type(foo) is list?

  • It is more likely your code will have some non-list sequence object, whereas non-string stringlike objects are pretty rare.

  • As Alex Martelli explains it is better to use isinstance than checking for exact type equality, so that you also include subclasses.

  • Specifically you probably want to handle both byte and Unicode strings the same way.

Alex says that testing for types is ugly, and in general I agree, but if you need to write out strings one way and lists another this is the right way to do it.

Community
  • 1
  • 1
poolie
  • 9,289
  • 1
  • 47
  • 74
2

Without knowing why exactly you are doing it this way - I'm sure you have a good reason, I would like to point out, that this is probably a code smell...

Most likely, you know before calling the function whether you have a list or a string. Write two functions or box the string in a list when calling the function. Voilà. No need to check the type.

If you don't know the kind of result you have, but should (because you want to treat it differently depending on the type) then that is also a code smell - somewhere in your code, you are not quite clear about what is happening or the interface of some subsystem is too ambiguous.

So. Either fix the underlying root cause. Or check the type inside your function.

Daren Thomas
  • 67,947
  • 40
  • 154
  • 200
  • In a nutshell, this is for a REST server which can get requests in various formats including AMF. Parameters can either be sent through query string (e.g. GET) meaning they are all strings or in the case of AMF, they are sent as is (since it's a binary format) – Ofir May 30 '13 at 07:32