2

I'm getting some practice with list comprehensions and I ran into the following error:

Find all of the numbers from 1-1000 that have a 3 in them

result = [i for i in range(1, 1001) if 3 in i]

print(result)

result = [i for i in range(1, 1000) if 3 in i]

TypeError: argument of type 'int' is not iterable

but it works perfectly if I write this code:

result = [i for i in range(1, 1001) if "3" in str(i)]
print(result)

...So clearly strings are iterable. But ints are not. Why?

TylerH
  • 20,799
  • 66
  • 75
  • 101
  • 1
    Does this answer your question? [why int object is not iterable while str is into python](https://stackoverflow.com/questions/20349284/why-int-object-is-not-iterable-while-str-is-into-python) – Ruzihm Jun 26 '20 at 17:21
  • It doesn't make sense to iterate over an integer. – Asocia Jun 26 '20 at 17:21
  • 1
    It's a design choice. There is nothing stopping whoever implemented `int` to having made the choice of making it iterable, they didn't. Probably, because there is no *obvious* choice as to how iteration over an integer would work, and generally, you don't *want* `int` objects to be iterable. For the use cases, there are already other ways to do it. – juanpa.arrivillaga Jun 26 '20 at 17:22
  • @juanpa.arrivillaga It would be really bad if they implemented it. Trying to slice an integer, iterating over it, indexing it... That's nonsense. If you need these operations, basically `int` is not the data type you're looking for. – Asocia Jun 26 '20 at 17:28
  • @Asocia I agree, it would be highly unexpected that `int` objects be iterable, that is probably the main reason it is the way it is. My point is simply that at the end of the day, it is a *choice* that was made. – juanpa.arrivillaga Jun 26 '20 at 17:30
  • "So clearly strings are iterable. But int's are not. BUT WHY??" So again, it isn't really clear to me what you are asking. The most direct answer to your question is "ints aren't iterable because they don't implement `__iter__`", is that what you are asking? – juanpa.arrivillaga Jun 26 '20 at 17:31
  • 2
    by the way, `if x in y` succeeding doesn't necessarily mean that `y` is iterable. You can make any class define a `__contains__` method to make it succeed with `if x in y` even if neither `__getitem__` nor `__iter__` are defined. – Ruzihm Jun 26 '20 at 17:33

4 Answers4

3

Because it’s not clear what iterating over an int would do. You seem to expect that it iterates over the decimal digits (?) but I for example find that unreasonable: I’d find it much more natural to iterate over its bits (i.e. its binary digits). Inside memory, ints are represented as binary numbers, not decimal numbers, so I could argue that mine is the more natural expectation.

But since there’s no obviously right answer, Python’s designers decided not to make int iterable at all.

By contrast, for strings there’s an immediately intuitive “right” answer (although the devil is in the details): we iterate over its characters. Unfortunately that actually opens a can of worms since whole books have been written about the definition of “character”, and in practice something subtly different happens (if you’re curious, start reading The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)). But the answer “we iterate over characters” is close enough.

Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
3

From a conceptual perspective, the answer by @KonradRudolph is the right one.

But from a technical perspective, what makes an object able to be iterated over? This property is called being iterable. And in Python, an object is iterable as long as it implements the magic method __iter__. Magic methods are special methods Python looks for in a class if it wants to use certain built-in behavior. Like iterating over an object, or adding two objects with +.

And so, we could easily implement our own number class that is iterable:

class IterableInt:
    def __init__(self, i):
        self.i = i
    
    def __iter__(self):
        return map(int, str(self.i))

Here I chose iterable to mean what you expected, i.e. iterating over the decimal digits. Iterating over the binary digits instead would only mean changing one line.

Note that this is not really a number (yet), you cannot add or multiply it. For that one would probably want to inherit from int, but that complicates things a bit more and is left as an exercise.

In any case, now we have an iterable integer:

n = IterableInt(123)
print(3 in n)
# True
print(list(n))
# [1, 2, 3]

If you need to find out if an object is iterable, there are two ways. One is called "Look before you leap" (LBYL), you can test for the existence of the __iter__ method or use the typing module:

hasattr(n, "__iter__")
# True
hasattr(123, "__iter__")
# False

from typing import Iterable
isinstance(n, Iterable)
# True
isinstance(123, Iterable)
# False

And the other is "it’s easier to ask for forgiveness than permission" (EAFP), where you just assume the object is iterable and deal with it if it is not:

try:
    3 in 123
except TypeError:
    print("too bad, should have used IterableInt")
Graipher
  • 6,891
  • 27
  • 47
0

Because a string is an array of characters. An int is not a sequence of anything (it can be represented as a sequence of digits, but that is not what it is).

Igor Rivin
  • 4,632
  • 2
  • 23
  • 35
  • 3
    I mean, an `int` is an array of bytes underneath the hood. This doesn't really explain it, and is't really relevant as to why it is iterable. There's nothing *preventing* `int` objects from being iterable. It is a design *choice* – juanpa.arrivillaga Jun 26 '20 at 17:21
  • 1
    @juanpa.arrivillaga yep! and as [this answer](https://stackoverflow.com/a/31370482/1092820) so helpfully indicates, that design choice was solidified with the rejection of [PEP-276](https://www.python.org/dev/peps/pep-0276/) – Ruzihm Jun 26 '20 at 17:23
  • Thank you. Can you explain a bit more. Im struggling to understand why an int is not a sequence. but a str is?? –  Jun 26 '20 at 17:23
  • 1
    @sandynuggetsxx what do you mean **why**? A sequence is any type that implements integer-based indexing with `my_sequence[i]`, and several other methods... but sequence types aren't the only iterables. What sort of answer are you even looking for? – juanpa.arrivillaga Jun 26 '20 at 17:25
  • so @juanpa.arrivillaga are you saying the only reason an int is not iterable is because thats just the way the made it when they created python?? So i shouldn't rack my brain to much and just understand that that's just the way it is? (I hope so because Ive been stuck on this for 40 mins) –  Jun 26 '20 at 17:26
  • @sandynuggetsxx that's pretty much always the answer to this sort of question... I'm not even sure *what you are asking*. Like what sort of answer do you expect? Are you asking *what iterable means*? – juanpa.arrivillaga Jun 26 '20 at 17:28
  • 1
    Note, a `str` isn't really an "array of characters", there are no "characters" in python. – juanpa.arrivillaga Jun 26 '20 at 17:29
  • @juanpa.arrivillaga I dont know python at all my! I am trying to learn it. Im a very curious person so everytime I make progress, I discover more nuances. I dont want any gaps in my learning, so I seek answers for everything. –  Jun 26 '20 at 17:29
  • 1
    @sandynuggetsxx ... that doesn't clarify your question to me. It isn't clear *what exactly* you are asking. – juanpa.arrivillaga Jun 26 '20 at 17:30
  • I guess im just wondering what makes strings capable of being iterable? and what prevents integers from being iterable. –  Jun 26 '20 at 17:38
  • @sandynuggetsxx The post I linked as a comment on the question here explains that. – Ruzihm Jun 26 '20 at 17:55
0

if you want to check the int, you have to match with every digit in the int by every tenth place.

from math import floor

def check_if_number_exists(number, digit_to_find):
    # check if the number is positive
    if number < 0:
        Number = -number
    else:
        Number = number
    while(Number != 0):
        # run loop for extracting each digit
        Digit = Number % 10 # gives the last number
        Number = floor(Number / 10) # removing the last digit
        if Digit == digit_to_find:
            print("the number contains " + str(digit_to_find) + ' in ' + str(number))
            break


check_if_number_exists(45638, 3)

you check any number you want with above code.

you cannot do the same way you did for str because int is different from string. A small example

s + e + v + e + n = seven (string is array which comprise of letters)
43567 = 40000 + 3000 + 500 + 60 + 7 (not 4+3+5+6+7)

hope you understand, why iteration dont work on int

Raady
  • 1,686
  • 5
  • 22
  • 46