code: def random_product(items, num=None, rng=None):
      
      import utool as ut
      rng = ut.ensure_rng(rng, 'python')
      seen = set()
      items = [list(g) for g in items]
      max_num = ut.prod(map(len, items))
      if num is None:
          num = max_num
      if num > max_num:
          raise ValueError('num exceedes maximum number of products')
  
      # TODO: make this more efficient when num is large
      if num > max_num // 2:
          for prod in ut.shuffle(list(it.product(*items)), rng=rng):
              yield prod
      else:
          while len(seen) < num:
              # combo = tuple(sorted(rng.choice(items, size, replace=False)))
              idxs = tuple(rng.randint(0, len(g) - 1) for g in items)
              if idxs not in seen:
                  seen.add(idxs)
                  prod = tuple(g[x] for g, x in zip(items, idxs))
                  yield prod
docs: Yields `num` items from the cartesian product of items in a random order.
  
      Args:
          items (list of sequences): items to get caresian product of
              packed in a list or tuple.
              (note this deviates from api of it.product)
  
      Example:
          import utool as ut
          items = [(1, 2, 3), (4, 5, 6, 7)]
          rng = 0
          list(ut.random_product(items, rng=0))
          list(ut.random_product(items, num=3, rng=0))