Repository: Lightning-AI/pytorch-lightning

Issue:
Fix exception chaining
I recently went over [PyTorch](https://github.com/pytorch/pytorch/pull/43836) and [Detectron2](https://github.com/facebookresearch/detectron2/pull/1945), suggesting a fix in the way that Python 3's exception chaining is used.

As described in detail in [this article](https://blog.ram.rachum.com/post/621791438475296768/improving-python-exception-chaining-with), exception chaining ([PEP 3134](https://www.python.org/dev/peps/pep-3134/)) can be used to make exceptions more user-friendly, and in that case, the syntax `raise new_exception from old_exception` needs to be used.

When `raise .. from ..` is used, there will be a line saying `The above exception was the direct cause of the following exception` between tracebacks. However, when implicitly chaining exceptions (meaning when `raise .. from ..` is not used), the message will be `During handling of the above exception, another exception occurred` which can confuse users.

Specifically, the following should be used in order to chain exceptions:
```python
try:
    something_which_raises_OldError
except OldError as e:
    raise NewError("A more user-friendly exception message.") from e 
```
instead of:
```python
try:
    something_which_raises_OldError
except OldError:
    raise NewError("A more user-friendly exception message.")
```

One example which needs to be fixed is:
https://github.com/PyTorchLightning/pytorch-lightning/blob/3d76f604bdb97d66dd1248540ea088fb18c06a03/pytorch_lightning/utilities/parsing.py#L159-L162

If this suggestion sounds good and reasonable, I'd be happy to create a PR! Let me know your thoughts on this!


Discussion:
Hi! thanks for your contribution!, great first issue!
oh! I just learned something new about Python exceptions. Awesome!
So if I understand correctly, this change will make the exceptions more user friendly, pointing to where it originated from. 
Do we keep the bugfix label? It sounds more like an enhancement

Related: #2266 

Thanks @edenlightning for relabelling the issue! I didn't know Hacktoberfest at all!

@awaelchli Thank you for your quick response!
Yes, you're right. Since this issue is not critical, can I work on this in October as a PR for Hacktoberfest?
@akihironitta assigned you to this issue. Yeah, you can do this in October.

Resolving patch:
diff --git a/pytorch_lightning/accelerators/ddp2_backend.py b/pytorch_lightning/accelerators/ddp2_backend.py
--- a/pytorch_lightning/accelerators/ddp2_backend.py
+++ b/pytorch_lightning/accelerators/ddp2_backend.py
@@ -47,9 +47,9 @@ def _resolve_task_idx(self):
             # torchelastic or general non_slurm ddp2
             try:
                 self.task_idx = int(os.environ['LOCAL_RANK'])
-            except Exception as e:
+            except Exception as exp:
                 m = 'ddp2 only works in SLURM or via torchelastic with the WORLD_SIZE, LOCAL_RANK, GROUP_RANK flags'
-                raise MisconfigurationException(m)
+                raise MisconfigurationException(m) from exp
 
     def train(self):
         model = self.trainer.model
diff --git a/pytorch_lightning/trainer/logging.py b/pytorch_lightning/trainer/logging.py
--- a/pytorch_lightning/trainer/logging.py
+++ b/pytorch_lightning/trainer/logging.py
@@ -141,13 +141,13 @@ def process_dict_result(self, output, train=False):
         if train:
             try:
                 loss = output['loss']
-            except Exception:
+            except Exception as exp:
                 if isinstance(output, torch.Tensor):
                     loss = output
                 else:
                     raise RuntimeError(
                         'No `loss` value in the dictionary returned from `model.training_step()`.'
-                    )
+                    ) from exp
 
             # when using dp need to reduce the loss
             if self.use_dp or self.use_ddp2:
diff --git a/pytorch_lightning/utilities/parsing.py b/pytorch_lightning/utilities/parsing.py
--- a/pytorch_lightning/utilities/parsing.py
+++ b/pytorch_lightning/utilities/parsing.py
@@ -158,8 +158,8 @@ class AttributeDict(Dict):
     def __getattr__(self, key):
         try:
             return self[key]
-        except KeyError:
-            raise AttributeError(f'Missing attribute "{key}"')
+        except KeyError as exp:
+            raise AttributeError(f'Missing attribute "{key}"') from exp
 
     def __setattr__(self, key, val):
         self[key] = val


Test patch:
diff --git a/tests/base/models.py b/tests/base/models.py
--- a/tests/base/models.py
+++ b/tests/base/models.py
@@ -10,9 +10,9 @@
 
 try:
     from test_tube import HyperOptArgumentParser
-except ImportError:
+except ImportError as exp:
     # TODO: this should be discussed and moved out of this package
-    raise ImportError('Missing test-tube package.')
+    raise ImportError('Missing test-tube package.') from exp
 
 from pytorch_lightning.core.lightning import LightningModule