Repository: Lightning-AI/pytorch-lightning
File: pytorch_lightning/trainer/connectors/data_connector.py (Python)

Code before:
        # We use datamodule if it's been provided on .fit or .test, otherwise we check model for it
        datamodule = datamodule or getattr(model, 'datamodule', None)

        # If we have a datamodule, attach necessary hooks + dataloaders
        if datamodule:

            # Override loader hooks
            if is_overridden('train_dataloader', datamodule):
                model.train_dataloader = datamodule.train_dataloader
            if is_overridden('val_dataloader', datamodule):
                model.val_dataloader = datamodule.val_dataloader
            if is_overridden('test_dataloader', datamodule):
                model.test_dataloader = datamodule.test_dataloader
            if is_overridden('predict_dataloader', datamodule):
                model.predict_dataloader = datamodule.predict_dataloader

            # Override transfer_batch_to_device if dataset-specific to_device logic has been defined in datamodule
            if is_overridden('transfer_batch_to_device', datamodule):
                model.transfer_batch_to_device = datamodule.transfer_batch_to_device

            self.trainer.datamodule = datamodule
            datamodule.trainer = self.trainer


class _PatchDataLoader(object):
    r"""
    Callable object for patching dataloaders passed into trainer.fit().
    Use this class to override model.*_dataloader() and be pickle-compatible.

    Args:
        dataloader: Dataloader object to return when called.

    """

    def __init__(self, dataloader: Union[List[DataLoader], DataLoader]):
        self.dataloader = dataloader

        # cannot pickle __code__ so cannot verify if PatchDataloader
        # exists which shows dataloader methods have been overwritten.
        # so, we hack it by using the string representation
        self.patch_loader_code = str(self.__call__.__code__)

    def __call__(self) -> Union[List[DataLoader], DataLoader]:
        return self.dataloader


Review comment (suggestion):
I know you didn't "invent" this but I would like to comment anyway that patching the model's methods like this is bad and we should make an effort to avoid this in the future. The same is done with data loaders. The user doesn't really know this, we are essentially destroying the definition of the user's model. We also never reset it, i.e., if the user want's to perform some operations with the model after fit, it will misbehave.
I will finally open an issue for this :) 

Code after:
        # We use datamodule if it's been provided on .fit or .test, otherwise we check model for it
        datamodule = datamodule or getattr(model, 'datamodule', None)

        # If we have a datamodule, attach necessary hooks + dataloaders
        if datamodule:

            # Override loader hooks
            if is_overridden('train_dataloader', datamodule):
                model.train_dataloader = datamodule.train_dataloader
            if is_overridden('val_dataloader', datamodule):
                model.val_dataloader = datamodule.val_dataloader
            if is_overridden('test_dataloader', datamodule):
                model.test_dataloader = datamodule.test_dataloader
            if is_overridden('predict_dataloader', datamodule):
                model.predict_dataloader = datamodule.predict_dataloader

            # Override data transfer hooks if dataset-specific to_device logic has been defined in datamodule
            if is_overridden('on_before_batch_transfer', datamodule):
                model.on_before_batch_transfer = datamodule.on_before_batch_transfer
            if is_overridden('transfer_batch_to_device', datamodule):
                model.transfer_batch_to_device = datamodule.transfer_batch_to_device
            if is_overridden('on_after_batch_transfer', datamodule):
                model.on_after_batch_transfer = datamodule.on_after_batch_transfer

            self.trainer.datamodule = datamodule
            datamodule.trainer = self.trainer


class _PatchDataLoader(object):
    r"""
    Callable object for patching dataloaders passed into trainer.fit().
    Use this class to override model.*_dataloader() and be pickle-compatible.

    Args:
        dataloader: Dataloader object to return when called.

    """

    def __init__(self, dataloader: Union[List[DataLoader], DataLoader]):
        self.dataloader = dataloader

        # cannot pickle __code__ so cannot verify if PatchDataloader
        # exists which shows dataloader methods have been overwritten.
        # so, we hack it by using the string representation
        self.patch_loader_code = str(self.__call__.__code__)

    def __call__(self) -> Union[List[DataLoader], DataLoader]:
        return self.dataloader