def __init__(
        self,
        datasets: Sequence[IDataset[T_co]],
        *,
        indices: Optional[List[int]] = None,
        data_attributes: Optional[List[DataAttribute]] = None,
        transform_groups: Optional[TransformGroups] = None,
        frozen_transform_groups: Optional[TransformGroups] = None,
        collate_fn: Optional[Callable[[List], Any]] = None,
    ):
        """Creates a ``AvalancheDataset`` instance.

        :param dataset: Original dataset. Beware that
            AvalancheDataset will not overwrite transformations already
            applied by this dataset.
        :param transform_groups: Avalanche transform groups.
        """
        if issubclass(type(datasets), TorchDataset) or issubclass(
            type(datasets), AvalancheDataset
        ):
            datasets = [datasets]  # type: ignore

        # NOTES on implementation:
        # - raw datasets operations are implemented by _FlatData
        # - data attributes are implemented by DataAttribute
        # - transformations are implemented by TransformGroups
        # AvalancheDataset just takes care to manage all of these attributes
        # together and decides how the information propagates through
        # operations (e.g. how to pass attributes after concat/subset
        # operations).
        flat_datas = []
        for d in datasets:
            if len(d) > 0:
                if isinstance(d, AvalancheDataset):
                    flat_datas.append(d._flat_data)
                elif not isinstance(d, _FlatDataWithTransform):
                    flat_datas.append(_FlatDataWithTransform([d]))
                else:
                    flat_datas.append(d)
        if (
            transform_groups is None
            and frozen_transform_groups is None
            and indices is not None
            and len(flat_datas) == 1
        ):
            # TODO: remove. shouldn't be needed but helps with flattening
            assert len(flat_datas) == 1
            self._flat_data = flat_datas[0].subset(indices)
        elif (
            transform_groups is None
            and frozen_transform_groups is None
            and indices is None
            and len(flat_datas) >= 1
        ):
            # TODO: remove. shouldn't be needed but helps with flattening
            if len(flat_datas) == 0:
                self._flat_data = _FlatDataWithTransform([])
            self._flat_data = flat_datas[0]
            if not isinstance(self._flat_data, _FlatDataWithTransform):
                self._flat_data = _FlatDataWithTransform([self._flat_data])

            for d in flat_datas[1:]:
                if not isinstance(d, _FlatDataWithTransform):
                    d = _FlatDataWithTransform([d])
                self._flat_data = self._flat_data.concat(d)
        else:
            self._flat_data: _FlatDataWithTransform[T_co] = _FlatDataWithTransform(
                flat_datas,
                indices=indices,
                transform_groups=transform_groups,
                frozen_transform_groups=frozen_transform_groups,
            )
        self.collate_fn = collate_fn

        ####################################
        # Init collate_fn
        ####################################
        if len(datasets) > 0:
            self.collate_fn = self._init_collate_fn(datasets[0], collate_fn)
        else:
            self.collate_fn = default_collate
        """
        The collate function to use when creating mini-batches from this
        dataset.
        """

        ####################################
        # Init data attributes
        ####################################
        # concat attributes from child datasets
        new_data_attributes: Dict[str, DataAttribute] = dict()
        if data_attributes is not None:
            new_data_attributes = {da.name: da for da in data_attributes}
            ld = sum(len(d) for d in datasets)
            for da in data_attributes:
                if len(da) != ld:
                    raise ValueError(
                        "Data attribute {} has length {} but the dataset "
                        "has length {}".format(da.name, len(da), ld)
                    )

        self._data_attributes: Dict[str, DataAttribute] = OrderedDict()
        first_dataset = datasets[0] if len(datasets) > 0 else None
        if isinstance(first_dataset, AvalancheDataset):
            for attr in first_dataset._data_attributes.values():
                if attr.name in new_data_attributes:
                    # Keep overridden attributes in their previous position
                    self._data_attributes[attr.name] = new_data_attributes.pop(
                        attr.name
                    )
                    continue

                acat = attr
                found_all = True
                for d2 in datasets[1:]:
                    if hasattr(d2, attr.name):
                        acat = acat.concat(getattr(d2, attr.name))
                    elif len(d2) > 0:  # if empty we allow missing attributes
                        found_all = False
                        break
                if found_all:
                    self._data_attributes[attr.name] = acat

        # Insert new data attributes after inherited ones
        for da in new_data_attributes.values():
            self._data_attributes[da.name] = da

        if indices is not None:  # subset operation for attributes
            for da in self._data_attributes.values():
                # TODO: this was the old behavior. How do we know what to do if
                # we permute the entire dataset?
                # DEPRECATED! always subset attributes
                # we keep this behavior only for `classification_subset`
                # if len(da) != sum([len(d) for d in datasets]):
                #     self._data_attributes[da.name] = da
                # else:
                #     self._data_attributes[da.name] = da.subset(self._indices)
                #
                #     dasub = da.subset(indices)
                #     self._data_attributes[da.name] = dasub
                dasub = da.subset(indices)
                self._data_attributes[da.name] = dasub

        # set attributes dynamically
        for el in self._data_attributes.values():
            assert len(el) == len(self), f"BUG: Wrong size for attribute {el.name}"

            is_property = False
            if hasattr(self, el.name):
                is_property = True
                # Do not raise an error if a property.
                # Any check related to the property will be done
                # in the property setter method.
                if not isinstance(getattr(type(self), el.name, None), property):
                    raise ValueError(
                        f"Trying to add DataAttribute `{el.name}` to "
                        f"AvalancheDataset but the attribute name is "
                        f"already used."
                    )
            if not is_property:
                setattr(self, el.name, el)
Creates a ``AvalancheDataset`` instance.

        :param dataset: Original dataset. Beware that
            AvalancheDataset will not overwrite transformations already
            applied by this dataset.
        :param transform_groups: Avalanche transform groups.
        
avalanche/benchmarks/utils/data.py