Repository: biopython/biopython

Issue:
Loading substitution matrices causes ResourceWarning
### Setup

I am reporting a problem with Biopython version, Python version, and operating
system as follows:

```python
import sys; print(sys.version)
import platform; print(platform.python_implementation()); print(platform.platform())
import Bio; print(Bio.__version__)
```

3.8.10 (pipelines/36268:97041de, Jun  4 2021, 13:53:01) [MSC v.1916 64 bit (AMD64)]
CPython
Windows-10-10.0.19045-SP0
1.81

### Issue
loading a substitution matrix with `Bio.Align.substitution_matrices.load` causes a `ResourceWarning`

### Steps to reproduce
the following minimal snippet should reproduce this issue
```
from Bio.Align import substitution_matrices

def main():
    blosum_62 = substitution_matrices.load("BLOSUM62")
    assert blosum_62["A", "A"] == 4.0

if __name__ == '__main__':
    main()
```

I am pretty sure the issue is in `Bio.Align.substitution_matrices.__init__.read`; the `finally` clause on line 465 should be un-indented to apply to the outer try-except instead of the inner. better still, this file handling could be reworked to use a context manager



Discussion:

Resolving patch:
diff --git a/Bio/Align/substitution_matrices/__init__.py b/Bio/Align/substitution_matrices/__init__.py
--- a/Bio/Align/substitution_matrices/__init__.py
+++ b/Bio/Align/substitution_matrices/__init__.py
@@ -11,6 +11,8 @@
 import string
 import numpy as np
 
+from Bio.File import as_handle
+
 
 class Array(np.ndarray):
     """numpy array subclass indexed by integers and by letters."""
@@ -455,17 +457,9 @@ def __repr__(self):
 
 def read(handle, dtype=float):
     """Parse the file and return an Array object."""
-    try:
-        fp = open(handle)
+    with as_handle(handle) as fp:
         lines = fp.readlines()
-    except TypeError:
-        fp = handle
-        try:
-            lines = fp.readlines()
-        except Exception as e:
-            raise e from None
-        finally:
-            fp.close()
+
     header = []
     for i, line in enumerate(lines):
         if not line.startswith("#"):


Test patch:
diff --git a/Tests/test_align_substitution_matrices.py b/Tests/test_align_substitution_matrices.py
--- a/Tests/test_align_substitution_matrices.py
+++ b/Tests/test_align_substitution_matrices.py
@@ -2412,6 +2412,29 @@ def test_loading(self):
             except Exception:
                 self.fail(f"Failed to load subsitution matrix '{name}'")
 
+    def test_reading(self):
+        """Confirm matrix reading works with filename or handle."""
+        matrix_name = "BLOSUM62"
+        test_path = os.path.dirname(__file__)
+        parent_dir = os.path.dirname(test_path)
+        sub_mx_dir = os.path.join(
+            parent_dir, "Bio", "Align", "substitution_matrices", "data"
+        )
+        matrix_path = os.path.join(sub_mx_dir, matrix_name)
+
+        fname_matrix = substitution_matrices.read(matrix_path)
+        self.assertAlmostEqual(fname_matrix["A"]["A"], 4.0)
+        self.assertEqual(len(fname_matrix), 24)
+        self.assertEqual(len(fname_matrix[0]), 24)
+
+        with open(matrix_path, "r") as handle:
+            handle_matrix = substitution_matrices.read(handle)
+            self.assertFalse(handle.closed)
+        self.assertTrue(handle.closed)
+        self.assertAlmostEqual(handle_matrix["A"]["A"], 4.0)
+        self.assertEqual(len(handle_matrix), 24)
+        self.assertEqual(len(handle_matrix[0]), 24)
+
 
 if __name__ == "__main__":
     runner = unittest.TextTestRunner(verbosity=2)