def reader(data_files, names, batch_size=128, nb_sample=None, shuffle=False,
           loop=False):
    if isinstance(names, dict):
        names = hnames_to_names(names)
    else:
        names = to_list(names)
    # Copy, since list will be changed if shuffle=True
    data_files = list(to_list(data_files))

    # Check if names exist
    h5_file = h5.File(data_files[0], 'r')
    for name in names:
        if name not in h5_file:
            raise ValueError('%s does not exist!' % name)
    h5_file.close()

    if nb_sample:
        # Select the first k files s.t. the total sample size is at least
        # nb_sample. Only these files will be shuffled.
        _data_files = []
        nb_seen = 0
        for data_file in data_files:
            h5_file = h5.File(data_file, 'r')
            nb_seen += len(h5_file[names[0]])
            h5_file.close()
            _data_files.append(data_file)
            if nb_seen >= nb_sample:
                break
        data_files = _data_files
    else:
        nb_sample = np.inf

    file_idx = 0
    nb_seen = 0
    while True:
        if shuffle and file_idx == 0:
            np.random.shuffle(data_files)

        h5_file = h5.File(data_files[file_idx], 'r')
        data_file = dict()
        for name in names:
            data_file[name] = h5_file[name]
        nb_sample_file = len(list(data_file.values())[0])

        if shuffle:
            # Shuffle data within the entire file, which requires reading
            # the entire file into memory
            idx = np.arange(nb_sample_file)
            np.random.shuffle(idx)
            for name, value in six.iteritems(data_file):
                data_file[name] = value[:len(idx)][idx]

        nb_batch = int(np.ceil(nb_sample_file / batch_size))
        for batch in range(nb_batch):
            batch_start = batch * batch_size
            nb_read = min(nb_sample - nb_seen, batch_size)
            batch_end = min(nb_sample_file, batch_start + nb_read)
            _batch_size = batch_end - batch_start
            if _batch_size == 0:
                break

            data_batch = dict()
            for name in names:
                data_batch[name] = data_file[name][batch_start:batch_end]
            yield data_batch

            nb_seen += _batch_size
            if nb_seen >= nb_sample:
                break

        h5_file.close()
        file_idx += 1
        assert nb_seen <= nb_sample
        if nb_sample == nb_seen or file_idx == len(data_files):
            if loop:
                file_idx = 0
                nb_seen = 0
            else:
                break