code: def check_param(param, param_name, dtype, constraint=None, iterable=True,
                  max_depth=2):
      
      msg = []
      msg.append(param_name + " must be "+ dtype)
      if iterable:
          msg.append(" or nested iterable of depth " + str(max_depth) +
                     " containing " + dtype + "s")
  
      msg.append(", but found " + param_name + " = {}".format(repr(param)))
  
      if constraint is not None:
          msg = (" " + constraint).join(msg)
      else:
          msg = ''.join(msg)
  
      # check param is numerical
      try:
          param_dt = np.array(flatten(param))# + np.zeros_like(flatten(param), dtype='int')
          # param_dt = np.array(param).astype(dtype)
      except (ValueError, TypeError):
          raise TypeError(msg)
  
      # check iterable
      if iterable:
          if check_iterable_depth(param) > max_depth:
              raise TypeError(msg)
      if (not iterable) and isiterable(param):
          raise TypeError(msg)
  
      # check param is correct dtype
      if not (param_dt == np.array(flatten(param)).astype(float)).all():
          raise TypeError(msg)
  
      # check constraint
      if constraint is not None:
          if not (eval('np.' + repr(param_dt) + constraint)).all():
              raise ValueError(msg)
  
      return param
docs: checks the dtype of a parameter,
      and whether it satisfies a numerical contraint
  
      Parameters
      ---------
      param : object
      param_name : str, name of the parameter
      dtype : str, desired dtype of the parameter
      contraint : str, default: None
                  numerical constraint of the parameter.
                  if None, no constraint is enforced
      iterable : bool, default: True
                 whether to allow iterable param
      max_depth : int, default: 2
                  maximum nesting of the iterable.
                  only used if iterable == True
      Returns
      -------
      list of validated and converted parameter(s)