docs: def attr_membership(attr_val, value_set, attr_type=basestring,
                            modifier_fn=lambda x: x):
      """
      Helper function passed to netCDF4.Dataset.get_attributes_by_value
      Checks that `attr_val` exists, has the same type as `attr_type`,
      and is contained in `value_set`
      attr_val: The value of the attribute being checked
      attr_type: A type object that the `attr_val` is expected to have the same
                 type as.  If the type is not the same, a warning is issued and
                 the code attempts to cast `attr_val` to the expected type.
      value_set: The set against which membership for `attr_val` is tested
      modifier_fn: A function to apply to attr_val prior to applying the set
                   membership test
  
      """
      if attr_val is None:
          return False
  
      if not isinstance(attr_val, attr_type):
          warnings.warn("Attribute is of type {}, {} expected. "
                        "Attempting to cast to expected type.".format(type(attr_val),
                                                                      attr_type))
          try:
              # if the expected type is basestring, try casting to unicode type
              # since basestring can't be instantiated
              if attr_type is basestring:
                  new_attr_val = six.text_type(attr_val)
              else:
                  new_attr_val = attr_type(attr_val)
          # catch casting errors
          except (ValueError, UnicodeEncodeError) as e:
              warnings.warn("Could not cast to type {}".format(attr_type))
              return False
      else:
          new_attr_val = attr_val
  
      try:
          is_in_set = modifier_fn(new_attr_val) in value_set
      except Exception as e:
          warnings.warn('Could not apply modifier function {} to value: '
                        ' {}'.format(modifier_fn, e.msg))
          return False
  
      return is_in_set