1

In my work, my Python scripts get a lot of input from non-Python-professional users. So, for example, if my function needs to handle both single values and a series of values (Polymorphism/Duck Typing, Yo!) I would like to do something like this pseudo-code:

def duck_typed(args):
    """
    args - Give me a single integer or a list of integers to work on.

    """
    for li in args:
        <do_something>

If the user passes me a list:

[1,2]

a tuple:

(1,2)

or even a singleton tuple (terminology help here, please)

(1,)

everything works as expected. But as soon as the user passes in a single integer everything goes to $%^&:

1

TypeError: 'int' object is not iterable  


The Difference between Smart and Clever

Now, I immediately think, "No problem, I just need to get clever!" and I do something like this:

def duck_typed(args):
    """
    args - Give me a single integer or a list of integers to work on.

    """
    args = (args,)  # <= Ha ha!  I'm so clever!

    for li in args:
        <do_something>


Well, now it works for the single integer case:

args = (1,)

(1,)

but when the user passes in an iterable, the $%^&*s come out again. My trick gives me a nested iterable:

args = ((1,2),)

((1,2),)

ARGH!


The Usual Suspects

There are of course the usual workarounds. Try/except clauses:

try:
    args = tuple(args)
except TypeError:
    args = tuple((args,))


These work, but I run into this issue A LOT. This is really a 1-line problem and try/except is a 4-line solution. I would really love it if I could just call:

tuple(1)

have it return (1,) and call it a day.


Other People Use this Language Too, You Know

Now I'm aware that my needs in my little corner of the Python programming universe don't apply to the rest of the Python world. Being dynamically typed makes Python such a wonderful language to work in -- especially after years of work in neurotic languages such as C. (Sorry, sorry. I'm not bashing C. It's quite good at what its good at, but you know: xkcd)

I'm quite sure the creators of the Python language have a very good reason to not allow tuple(1).


Question 1

Will someone please explain why the creators chose to not allow tuple(1) and/or list(1) to work? I'm sure its completely sane reason and bloody obvious to many. I may have just missed it during my tenure at the School of Hard Knocks. (It's on the other side of the hill from Hogwarts.)

Question 2

Is there a more practical -- hopefully 1-line -- way to make the following conversion?

X(1) -> (1,)
X((1,2)) -> (1,2)

If not, I guess I could just break down and roll my own.

user2864740
  • 60,010
  • 15
  • 145
  • 220
JS.
  • 14,781
  • 13
  • 63
  • 75
  • The fact that you want to make such a conversion indicates bad design. I'd suggest writing a separate function for the plain int case. – arshajii Apr 24 '14 at 23:28
  • Why is writing a method that works on any object "Duck Typing/Polymorphism" but wanting to work on a single integer or a list of them "Bad Design"? – JS. Apr 24 '14 at 23:30
  • 1
    @JS. Because it normally indicates a change in multiplicity requirements (not count). These are difference concepts: a *value* and a *sequence* of [zero or more] values. – user2864740 Apr 24 '14 at 23:41
  • (You will get better responses if you focus on the problem/issue instead of begging opinions.) – user2864740 Apr 24 '14 at 23:44
  • @user2864740: Fine. But if I have to perform the same, complex operation on a single digit as I have to perform on hundreds, why does Polymorphism go out the window? – JS. Apr 24 '14 at 23:44
  • @user2864740: Please explain why asking *why* 'list(1)' doesn't work is asking for an opinion? – JS. Apr 24 '14 at 23:46
  • @user2864740: So the answer is, "It doesn't because it doesn't"? – JS. Apr 24 '14 at 23:47

2 Answers2

3

Duck typing explains and validates why list(1) fails.

The method was expecting a Duck but was given a Hamster, and Hamsters can't swim1.

In duck typing, a programmer is only concerned with ensuring that objects behave as demanded of them in a given context, rather than ensuring that they are of a specific type.

But not all objects/types behave the same, or "as demanded". In this case, an integer does not behave like an iterable and causes an exception. However, list("quack") works precisely because a string does act like an iterable and goes Quack - ['q','u','a','c','k']! To make list take a non-iterable would actually mean special casing, not duck typing.

Expecting an integer to "be iterable" sounds like a design issue because this requires an implicit change in multiplicity. That is, the concepts of a value and a sequence of [zero or more] values should be kept separate. Polymorphism doesn't apply in this case as polymorphism (of any type) only works over unification - and there is no unification to "iterate a non-iterable".

Furthermore, having list(itr) only accept an iterable fits in the strongly-typed Python model and avoids edge-cases. Consider that if list(x) was written in such a way that it allowed a non-iterable as well, one could not determine if the result would be [x] or [x0..xn] without knowing the value supplied. Python simply forbids this operation and puts the burden of changing multiplicity - or, passing a Duck - on the calling code.


See In Python, how do I determine if an object is iterable? which presents several solutions to wrap (or otherwise deal with) a non-iterable value.

While I do not recommend this approach, as it changes the multiplicity, I would likely write such a coercion function as follows. Unlike isinstance checks it will handle all non-iterable values. However, you will have to work out the rules for what should happen on ensure_iterable(None).

def ensure_iterable(x):        
    try:
        return iter(x)
    except TypeError:
        return (x,)

And then "one line" usage:

for li in ensure_iterable(args):
    pass

1 Hamsters can swim .. or at least stay afloat for a little bit. However I find the analogy is apt (and more memorable) precisely because a wet/drowning hamster is a sad thought. Keep those little critters safe and dry!

Community
  • 1
  • 1
user2864740
  • 60,010
  • 15
  • 145
  • 220
2

Try this:

if isinstance(variable, int):
   variable = (variable, )
dstromberg
  • 6,954
  • 1
  • 26
  • 27
  • Good point. I do use that one quite a bit and overlooked it here. Definitely better. This form usually brings a lot of screaming from The Community to "use Duck Typing!" – JS. Apr 24 '14 at 23:34
  • @JS. Which is why it *failed* with [Duck typing](http://en.wikipedia.org/wiki/Duck_typing). The method was expecting a Duck but was given a Hamster, and Hamsters can't swim. "In duck typing, a programmer is only concerned with *ensuring that objects behave as demanded* of them in a given context, rather than ensuring that they are of a *specific* type" - but not all objects/types behave the same, or "as demanded". – user2864740 Apr 25 '14 at 00:01