isiterable Python3

usage

>>> isiterable(MyIterableContainer('Yaruo', 'Yaranaio', 'Yarumi'), 3)
True
>>> 
>>> isiterable(MyIterableContainer('Yaruo', 'Yaranaio', 'Yarumi'), 2)
False
>>>
>>>
>>> isiterable([1, 2, 3], 3)
True
>>> 
>>> isiterable([1, 2, 3], 2)
False

isiterable

from copy import deepcopy


def isiterable(obj, num_of_elements):
  if not(isinstance(num_of_elements, int) \
      and num_of_elements >= 0):
    raise ValueError('num_of_element sholud \
        be zero or a natural number.')
  
  # 1) container.__iter__()
  if not hasmethod(obj, '__iter__'): return False  
  iterator = obj.__iter__()


  # 2) iterator.__iter__()
  # Does this function return self?
  if not hasmethod(iterator, '__iter__'): return False
  if not iterator is iterator.__iter__(): return False


  # 3) iterator.__next__()
  # After popping all elements,
  # does this function raise StopIteration?
  if not hasmethod(iterator, '__next__'): return False
  itr = deepcopy(iterator)
  for k in range(num_of_elements+1):
    try:
      itr.__next__()
   
    except StopIteration:
      # 3-1) raised StopIteration, 
      #      but some elements still remain.
      if k < num_of_elements: return False

      # success
      # will break from this for-statement
      if k == num_of_elements: pass

    except:
      # 3-2) raised an error,
      #      but the error is not StopIteration
      return False

    else:
      # 3-3) object method __next__ called,
      #      but more than num_of_elements
      if k == num_of_elements: return False

  return True


def hasmethod(obj, method_name):
  return hasattr(obj, method_name) \
    and callable(getattr(obj, method_name))