docs: def read_data(f, grp, name, options, dsetgrp=None):
      """ Writes a piece of data into an open HDF5 file.
  
      Low level function to read a Python type of the specified name from
      specified Group.
  
      .. versionchanged:: 0.2
         Added argument `dsetgrp`.
  
      Parameters
      ----------
      f : h5py.File
          The open HDF5 file.
      grp : h5py.Group or h5py.File
          The Group to read the data from.
      name : str
          The name of the data to read.
      options : hdf5storage.core.Options
          The options to use when reading.
      dsetgrp : h5py.Dataset or h5py.Group or None, optional
          The Dataset or Group object to read if that has already been
          obtained and thus should not be re-obtained (``None``
          otherwise). If given, overrides `grp` and `name`.
  
      Returns
      -------
      data
          The data named `name` in Group `grp`.
  
      Raises
      ------
      CantReadError
          If the data cannot be read successfully.
  
      See Also
      --------
      hdf5storage.read : Higher level version.
      write_data
      hdf5storage.Options
  
      """
      if dsetgrp is None:
          # If name isn't found, return error.
          try:
              dsetgrp = grp[name]
          except:
              raise hdf5storage.exceptions.CantReadError( \
                  'Could not find ' + posixpath.join(grp.name, name))
  
      # Get all attributes with values.
      if sys.hexversion >= 0x03000000:
          defaultfactory = type(None)
      else:
          defaultfactory = lambda : None
      attributes = collections.defaultdict(defaultfactory,
                                           dsetgrp.attrs.items())
  
      # Get the different attributes that can be used to identify they
      # type, which are the type string and the MATLAB class.
      type_string = convert_attribute_to_string(attributes['Python.Type'])
      matlab_class = convert_attribute_to_string( \
          attributes['MATLAB_class'])
  
      # If the type_string is present, get the marshaller for it. If it is
      # not, use the one for the matlab class if it is given. Otherwise,
      # use the fallback (NumpyScalarArrayMarshaller for both Datasets and
      # Groups). If calls to the marshaller collection to get the right
      # marshaller don't return one (return None), we also go to the
      # default). Also get whether we have the modules required to read it
      # accurately or not (approximately)
  
      m = None
      has_modules = False
      mc = options.marshaller_collection
      if type_string is not None:
          m, has_modules = mc.get_marshaller_for_type_string(type_string)
      elif matlab_class is not None:
          m, has_modules = \
              mc.get_marshaller_for_matlab_class(matlab_class)
      elif hasattr(dsetgrp, 'dtype'):
          # Numpy dataset
          m, has_modules = \
              mc.get_marshaller_for_type(dsetgrp.dtype.type)
      elif isinstance(dsetgrp, (h5py.Group, h5py.File)):
          # Groups and files are like Matlab struct
          m, has_modules = mc.get_marshaller_for_matlab_class('struct')
  
      if m is None:
          # use Numpy as a fallback
          m, has_modules = mc.get_marshaller_for_type(np.uint8)
  
      # If a marshaller was found, use it to write the data. Otherwise,
      # return an error.
  
      if m is not None:
          if has_modules:
              return m.read(f, dsetgrp, attributes, options)
          else:
              return m.read_approximate(f, dsetgrp, attributes, options)
      else:
          raise hdf5storage.exceptions.CantReadError('Could not read '
                                                     + dsetgrp.name)