code: def blk_nd(blk, shape):
      
      internals = (blk_1d(b, s) for b, s in zip(blk, shape))
      return product(*internals)
docs: Iterate through the blocks that cover an array.
  
      This function first iterates trough the blocks that recover
      the part of the array given by max_blk_coverage
      and then iterates with smaller blocks for the rest
      of the array.
  
      :param blk: the N-dimensional shape of the block
      :param shape: the N-dimensional shape of the array
      :return: a generator that yields the blocks
  
      Example:
  
          >>> result = list(blk_nd(blk=(5,3), shape=(11, 11)))
          >>> result[0]
          (slice(0, 5, None), slice(0, 3, None))
          >>> result[1]
          (slice(0, 5, None), slice(3, 6, None))
          >>> result[-1]
          (slice(10, 11, None), slice(9, 11, None))
  
      The generator yields blocks of size blk until
      it covers the part of the array given by
      :func:`max_blk_coverage` and then yields
      smaller blocks until it covers the full array.
  
      .. seealso::
  
          :py:func:`blk_nd_short`
            Yields blocks of fixed size