code: def not_null(val, zero_as_null=True):
      
  
      def can_iter(x):
          """
          Returns True for a non-empty iterable
          """
          try:
              any(x)
              return True
          except TypeError:
              return False
  
      def not_empty(x):
          """
          Returns true if x has length
          """
          if len(x):
              return True
          return False
  
  
      def exists(x):
          """
          Returns true if x
          """
          if x:
              return True
          return False
  
      def is_nan(x):
          """
          Returns True if x is nan
          """
          try:
              if np.isnan(x):
                  return True
          except TypeError:
              return False
          return False
  
      # return True iff you have a non-empty iterable
      # and False for an empty iterable (including an empty string)
      if can_iter(val):
          return not_empty(val)
      # if value is not iterable, return False for np.nan, None, 0, or False
      # & True for all else
      else:
          if is_nan(val):
              return False
          if not zero_as_null:
              if val == 0:
                  return True
          return exists(val)
docs: Comprehensive check to see if a value is null or not.
      Returns True for: non-empty iterables, True, non-zero floats and ints,
      non-emtpy strings.
      Returns False for: empty iterables, False, zero, empty strings.
  
      Parameters
      ----------
      val : any Python object
      zero_as_null: bool
          treat zero as null, default True
  
      Returns
      ---------
      boolean