Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python method to remove iterability

Suppose I have a function which can either take an iterable/iterator or a non-iterable as an argument. Iterability is checked with try: iter(arg).

Depending whether the input is an iterable or not, the outcome of the method will be different. Not when I want to pass a non-iterable as iterable input, it is easy to do: I’ll just wrap it with a tuple.

What do I do when I want to pass an iterable (a string for example) but want the function to take it as if it’s non-iterable? E.g. make that iter(str) fails.

Edit – my original intention:

I wanted to generalise the zip function in that it can zip iterables with non-iterables. The non-iterable would then repeat itself as often as the other iterables haven’t finished.

The only general solution fo me seems now to be, that I shouldn’t check inside the general_zip function (because of the string issues); but that instead I’ll have to add the repeat iterator to the argument before calling zip. (This actually saves me from inventing the general_zip function — although I still might because with a non-iterable as an input it would be unambiguous without the extra repeat.)

like image 790
Debilski Avatar asked Mar 20 '10 12:03

Debilski


2 Answers

The more I think about it, it seems like it’s not possible to do without type checking or passing argments to the function.

However, depending on the intention of the function, one way to handle it could be:

from itertools import repeat
func(repeat(string_iterable))

func still sees an iterable but it won’t iterate through the charaters of the string itself. And effectively, the argument works as if it’s a constant non-iterable.

like image 195
Debilski Avatar answered Nov 19 '22 07:11

Debilski


Whoo! It appears you want to be able to pass iterables as iterables, iterables as noniterables, noniterables as iterables, and noniterables as noniterables. Since you want to be able to handle every possibility, and the computer can not (yet) read minds, you are going to have to tell the function how you want the argument to be handled:

def foo_iterable(iterable):
    ...
def foo_noniterable(noniterable):
    ...

def foo(thing,isiterable=True):
    if isiterable:
        foo_iterable(thing)
    else:
        foo_noniterable(thing)

Apply foo to an iterable

foo(iterable)

Apply foo to an iterable as a non-iterable:

foo_noniterable(iterable)       # or
foo(iterable, isiterable=False)

Apply foo to a noniterable as a noniterable:

foo_noniterable(noniterable)       # or
foo(noniterable,isiterable=False)

Apply foo to a noniterable as an iterable:

foo((noniterable,))

PS. I'm a believer in small functions that do a single job well. They are easier to debug and unit-test. In general I would advise avoiding monolithic functions that behave differently depending on type. Yes, it puts a little extra burden on the developer to call exactly the function that is intended, but I think the advantages in terms of debugging and unit-testing more than make up for it.

like image 2
unutbu Avatar answered Nov 19 '22 09:11

unutbu