when to choose collections.iterable or '__iter__' attribute in python? [duplicate]

  • Last Update :
  • Techknowledgy :

An object is called iterable if we can get an iterator from it. Most built-in containers in Python like: list, tuple, string etc. are iterables.,A more elegant way of automatically iterating is by using the for loop. Using this, we can iterate over any object that can return an iterator, for example list, string, file etc.,So internally, the for loop creates an iterator object, iter_obj by calling iter() on the iterable.,It is not necessary that the item in an iterator object has to be exhausted. There can be infinite iterators (which never ends). We must be careful when handling such iterators.

We use the next() function to manually iterate through all the items of an iterator. When we reach the end and there is no more data to be returned, it will raise the StopIteration Exception. Following is an example.

# define a list
my_list = [4, 7, 0, 3]

# get an iterator using iter()
my_iter = iter(my_list)

# iterate through it using next()

# Output: 4
print(next(my_iter))

# Output: 7
print(next(my_iter))

# next(obj) is same as obj.__next__()

# Output: 0
print(my_iter.__next__())

# Output: 3
print(my_iter.__next__())

# This will raise error, no items left
next(my_iter)

Output

4
7
0
3
Traceback (most recent call last):
File "<string>", line 24, in <module>
      next(my_iter)
      StopIteration

A more elegant way of automatically iterating is by using the for loop. Using this, we can iterate over any object that can return an iterator, for example list, string, file etc.

>>>
for element in my_list:
   ...print(element)
   ...
   4
7
0
3

Is actually implemented as.

# create an iterator object from that iterable
iter_obj = iter(iterable)

# infinite loop
while True:
   try:
   # get the next item
element = next(iter_obj)
# do something with element
except StopIteration:
   #
if StopIteration is raised,
break from loop
break

If you do not have any idea about object-oriented programming, visit Python Object-Oriented Programming.

class PowTwo:
   ""
"Class to implement an iterator of powers of two ""
"

def __init__(self, max = 0):
   self.max = max

def __iter__(self):
   self.n = 0
return self

def __next__(self):
   if self.n <= self.max:
   result = 2 ** self.n
self.n += 1
return result
else:
   raise StopIteration

# create an object
numbers = PowTwo(3)

# create an iterable from the object
i = iter(numbers)

# Using next to get to the next iterator element
print(next(i))
print(next(i))
print(next(i))
print(next(i))
print(next(i))

Suggestion : 2

The itertools module has a few functions that can be used to address this task. The first is the itertools.dropwhile() function. To use it, you supply a function and an iterable. The returned iterator discards the first items in the sequence as long as the supplied function returns True. Afterward, the entirety of the sequence is produced.,To use such a function, you iterate over it using a for loop or use it with some other function that consumes an iterable (e.g., sum(), list(), etc.). For example:,The dropwhile() and islice() functions are mainly convenience functions that you can use to avoid writing rather messy code such as this:,This example is based on skipping the first items according to a test function. If you happen to know the exact number of items you want to skip, then you can use itertools.islice() instead. For example:

To manually consume an iterable, use the next() function and write your code to catch the StopIteration exception. For example, this example manually reads lines from a file:

with open('/etc/passwd') as f:
   try:
   while True:
   line = next(f)
print(line, end = '')
except StopIteration:
   pass

Normally, StopIteration is used to signal the end of iteration. However, if you’re using next() manually (as shown), you can also instruct it to return a terminating value, such as None, instead. For example:

with open('/etc/passwd') as f:
   while True:
   line = next(f, None)
if line is None:
   break
print(line, end = '')

The following interactive example illustrates the basic mechanics of what happens during iteration:

>>> items = [1, 2, 3]
>>> # Get the iterator
>>> it = iter(items) # Invokes items.__iter__()
>>> # Run the iterator
>>> next(it) # Invokes it.__next__()
1
>>> next(it)
2
>>> next(it)
3
>>> next(it)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
      StopIteration
      >>>

Typically, all you need to do is define an __iter__() method that delegates iteration to the internally held container. For example:

class Node:
   def __init__(self, value):
   self._value = value
self._children = []

def __repr__(self):
   return 'Node({!r})'.format(self._value)

def add_child(self, node):
   self._children.append(node)

def __iter__(self):
   return iter(self._children)

# Example
if __name__ == '__main__':
   root = Node(0)
child1 = Node(1)
child2 = Node(2)
root.add_child(child1)
root.add_child(child2)
for ch in root:
   print(ch)
# Outputs Node(1), Node(2)

If you want to implement a new kind of iteration pattern, define it using a generator function. Here’s a generator that produces a range of floating-point numbers:

def frange(start, stop, increment):
   x = start
while x < stop:
   yield x
x += increment

To use such a function, you iterate over it using a for loop or use it with some other function that consumes an iterable (e.g., sum(), list(), etc.). For example:

>>>
for n in frange(0, 4, 0.5):
   ...print(n)
   ...
   0
0.5
1.0
1.5
2.0
2.5
3.0
3.5
   >>>
   list(frange(0, 1, 0.125))[0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875] >>>

The mere presence of the yield statement in a function turns it into a generator. Unlike a normal function, a generator only runs in response to iteration. Here’s an experiment you can try to see the underlying mechanics of how such a function works:

>>> def countdown(n):
... print('Starting to count from', n)
... while n > 0:
... yield n
... n -= 1
... print('Done!')
...

>>> # Create the generator, notice no output appears
>>> c = countdown(3)
>>> c
<generator object countdown at 0x1006a0af0>

   >>> # Run to first yield and emit a value
   >>> next(c)
   Starting to count from 3
   3

   >>> # Run to the next yield
   >>> next(c)
   2

   >>> # Run to next yield
   >>> next(c)
   1

   >>> # Run to next yield (iteration stops)
   >>> next(c)
   Done!
   Traceback (most recent call last):
   File "<stdin>", line 1, in <module>
         StopIteration
         >>>

By far, the easiest way to implement iteration on an object is to use a generator function. In Recipe 4.2, a Node class was presented for representing tree structures. Perhaps you want to implement an iterator that traverses nodes in a depth-first pattern. Here is how you could do it:

class Node:
   def __init__(self, value):
   self._value = value
self._children = []

def __repr__(self):
   return 'Node({!r})'.format(self._value)

def add_child(self, node):
   self._children.append(node)

def __iter__(self):
   return iter(self._children)

def depth_first(self):
   yield self
for c in self:
   yield from c.depth_first()

# Example
if __name__ == '__main__':
   root = Node(0)
child1 = Node(1)
child2 = Node(2)
root.add_child(child1)
root.add_child(child2)
child1.add_child(Node(3))
child1.add_child(Node(4))
child2.add_child(Node(5))

for ch in root.depth_first():
   print(ch)
# Outputs Node(0), Node(1), Node(3), Node(4), Node(2), Node(5)

Suggestion : 3

An object of an immutable sequence type cannot change once it is created. (If the object contains references to other objects, these other objects may be mutable and may be changed; however, the collection of objects directly referenced by an immutable object cannot change.),Called by str(object) and the built-in functions format() and print() to compute the “informal” or nicely printable string representation of an object. The return value must be a string object.,The membership test operators (in and not in) are normally implemented as an iteration through a container. However, container objects can supply the following special method with a more efficient implementation, which also does not require the object be iterable.,User-defined method objects may be created when getting an attribute of a class (perhaps via an instance of that class), if that attribute is a user-defined function object or a class method object.

def __hash__(self):
   return hash((self.name, self.nick, self.color))
import sys
from types
import ModuleType

class VerboseModule(ModuleType):
   def __repr__(self):
   return f 'Verbose {self.__name__}'

def __setattr__(self, attr, value):
   print(f 'Setting {attr}...')
super().__setattr__(attr, value)

sys.modules[__name__].__class__ = VerboseModule
class Philosopher:
   def __init_subclass__(cls, /, default_name, **kwargs):
      super().__init_subclass__( ** kwargs) cls.default_name = default_name

      class AustralianPhilosopher(Philosopher, default_name = "Bruce"):
      pass
class A:
   x = C() # Automatically calls: x.__set_name__(A, 'x')
class A:
   pass

c = C()
A.x = c # The hook is not called
c.__set_name__(A, 'x') # Manually invoke the hook
class Meta(type):
   pass

class MyClass(metaclass = Meta):
   pass

class MySubclass(MyClass):
   pass

Suggestion : 4

Last Updated : 13 Jan, 2021,GATE CS 2021 Syllabus

Output:

H e l l o

Output

TypeError: 'int'
object is not iterable

Suggestion : 5

You can get an iterator from any object o by calling iter(o) if at least one of the following conditions holds true: a) o has an __iter__ method which returns an iterator object. An iterator is any object with an __iter__ and a __next__ (Python 2: next) method. b) o has a __getitem__ method.,You can get an iterator from any object o by calling iter(o) if at least one of the following conditions holds true: a) o has an __iter__ method which returns an iterator object. An iterator is any object with an __iter__ and a __next__ (Python 2: next) method. b) o has a __getitem__ method. ,When you use for item in o for some iterable object o, Python calls iter(o) and expects an iterator object as the return value. An iterator is any object which implements a __next__ (or next in Python 2) method and an __iter__ method.,By convention, the __iter__ method of an iterator should return the object itself (i.e. return self). Python then calls next on the iterator until StopIteration is raised. All of this happens implicitly, but the following demonstration makes it visible:

Checking for __iter__ works on sequence types, but it would fail on e.g. strings in Python 2. I would like to know the right answer too, until then, here is one possibility (which would work on strings, too):

from __future__
import print_function
try: some_object_iterator = iter(some_object) except TypeError as te: print(some_object, 'is not iterable')

...

try: _ = (e
   for e in my_object) except TypeError: print my_object, 'is not iterable'

The collections module provides some abstract base classes, which allow to ask classes or instances if they provide particular functionality, for example:

from collections.abc
import Iterable
if isinstance(e, Iterable): # e is iterable

Duck typing

try: iterator = iter(theElement) except TypeError: # not iterable
else: # iterable #
for obj in iterator: # pass

Use the Abstract Base Classes. They need at least Python 2.6 and work only for new-style classes.

from collections.abc
import Iterable #
import directly from collections
for Python < 3.3
if isinstance(theElement, Iterable): # iterable
else: # not iterable

I'd like to shed a little bit more light on the interplay of iter, __iter__ and __getitem__ and what happens behind the curtains. Armed with that knowledge, you will be able to understand why the best you can do is

try: iter(maybe_iterable) print('iteration will probably work') except TypeError: print('not iterable')

By convention, the __iter__ method of an iterator should return the object itself (i.e. return self). Python then calls next on the iterator until StopIteration is raised. All of this happens implicitly, but the following demonstration makes it visible:

import random class DemoIterable(object): def __iter__(self): print('__iter__ called') return DemoIterator() class DemoIterator(object): def __iter__(self): return self def __next__(self): print('__next__ called') r = random.randint(1, 10) if r == 5: print('raising StopIteration') raise StopIteration
return r

Iteration over a DemoIterable:

>>> di = DemoIterable() >>>
   for x in di: ...print(x)...__iter__ called __next__ called 9 __next__ called 8 __next__ called 10 __next__ called 3 __next__ called 10 __next__ called raising StopIteration

Calling iter with an instance of BasicIterable will return an iterator without any problems because BasicIterable implements __getitem__.

>>> b = BasicIterable() >>> iter(b) <iterator object at 0x7f1ab216e320>

However, it is important to note that b does not have the __iter__ attribute and is not considered an instance of Iterable or Sequence:

>>> from collections
import Iterable, Sequence >>> hasattr(b, '__iter__') False >>> isinstance(b, Iterable) False >>> isinstance(b, Sequence) False

I've been studying this problem quite a bit lately. Based on that my conclusion is that nowadays this is the best approach:

from collections.abc
import Iterable # drop `.abc`
with Python 2.7 or lower def iterable(obj): return isinstance(obj, Iterable)

The above has been recommended already earlier, but the general consensus has been that using iter() would be better:

def iterable(obj): try: iter(obj) except Exception: return False
else: return True

We've used iter() in our code as well for this purpose, but I've lately started to get more and more annoyed by objects which only have __getitem__ being considered iterable. There are valid reasons to have __getitem__ in a non-iterable object and with them the above code doesn't work well. As a real life example we can use Faker. The above code reports it being iterable but actually trying to iterate it causes an AttributeError (tested with Faker 4.0.2):

>>> from faker import Faker >>> fake = Faker() >>> iter(fake) # No exception, must be iterable <iterator object at 0x7f1c71db58d0> >>> list(fake) # Ooops Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/home/.../site-packages/faker/proxy.py", line 59, in __getitem__ return self._factory_map[locale.replace('-', '_')] AttributeError: 'int' object has no attribute 'replace'

Suggestion : 6

In computer science, an iterator is an object that allows a programmer to traverse through all the elements of a collection regardless of its specific implementation.,The for loop works on any iterable object. Actually, this is true of all iteration tools that scan objects from left to right in Python including for loops, the list comprehensions, and the map built-in function, etc.,The iteration protocol also is the reason that we've had to wrap some results in a list call to see their values all at once (Python ver. 3.x). Object that are iterable returns results one at a time, not in a physical list:,Though Python iteration tools call these functions automatically, we can use them to apply the iteration protocol manually, too.

  • iterable produces iterator via __iter__()
  • iterator = iterable.__iter__()
  • iterator produces a stream of values via next()
  • value = iterator.next()
    value = iterator.next()
       ...
  • iterable produces iterator via __iter__()
  • iterator = iterable.__iter__()
  • iterator produces a stream of values via next()
  • value = iterator.next()
    value = iterator.next()
       ...

    >>> # Python 3
    >>> iterable = [1,2,3]
    >>> iterator = iterable.__iter__() # or iterator = iter(iterable)
    >>> type(iterator)
    <type 'listiterator'>
       >>> value = iterator.__next__() # or value = next(iterator)
       >>> print(value)
       1
       >>> value = next(iterator)
       >>> print(value)
       2
       >>>
       >>> # Python 2
       >>> iterable = [1,2,3]
       >>> iterator = iterable.__iter__()
       >>> type(iterator)
       <type 'listiterator'>
          >>> value = iterator.next()
          >>> value
          1
          >>> value = next(iterator)
          >>> value
          2

    Note: For Python 2.x, the print is not a function but a statement. So, the right statement for the print is:

    >>>
    for x in [1, 2, 3, 4, 5]:
       ...print x ** 3,
       ...
       1 8 27 64 125

    As a way of understanding the file iterator, we'll look at how it works with a file. Open file objects have readline() method. This reads one line each time we call readline(), we advance to the next line. At the end of the file, an empty string is returned. We detect it to get out of the loop.

    >>> f = open('C:\\workspace\\Masters.txt') >>>
       f.readline()
    'Michelangelo Buonarroti \n' >>>
    f.readline()
    'Pablo Picasso\n' >>>
    f.readline()
    'Rembrandt van Rijn \n' >>>
    f.readline()
    'Leonardo Da Vinci \n' >>>
    f.readline()
    'Claude Monet \n' >>>
    f.readline()
    '\n'
    # Returns an empty string at end - of - file >>>
       f.readline()
    '' >>>

    Suggestion : 7
    class Person:
       name = "Nisha"
    age = 25
    country = "England"
    
    print(dir(Person))
    ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__',
       '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__',
       '__init__', '__init_subclass__', '__le__', '__lt__', '__module__',
       '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
       '__setattr__', '__sizeof__', '__str__', '__subclasshook__',
       '__weakref__', 'age', 'country', 'name'
    ]
    numbers = iter([2, 4, 6, 8, 10])
    
    print(type(numbers))
    
    print(next(numbers))
    print(next(numbers))
    print(next(numbers))
    print(next(numbers))
    print(next(numbers))
    # The next()
    function will raise StopIteration as it is exhausted
    print(next(numbers))
    <class 'list_iterator'>
       2
       4
       6
       8
       10
    
       ---------------------------------------------------------------------------
       StopIteration                             Traceback (most recent call last)
       <ipython-input-15-0cb721c5f355> in <module>()
                 11
                 12 # The bext() function will raise StopIteration as it is exhausted
             ---> 13 print(next(numbers))
    
             StopIteration:
    numbers = [2, 4, 6, 8, 10]
    
    print(dir(numbers))
    ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__',
       '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
       '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__',
       '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__',
       '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
       '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__',
       '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count',
       'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort'
    ]