Repository: pydicom/pydicom

Issue:
Pydicom - TypeError: unhashable type 'list'
**Describe the bug**
Printing/working with dataset causes error. Seems like some tags are unreadable by pydicom, got some of tags which causes error in example code

**Expected behavior**
Correct work, no errors. At least this dataset works for other dicom readers like Radiant/Horos, so I think the issue in this library.

**Steps To Reproduce**
dcmread this dicom file - print(dataset) - ??? - TypeError
```python
corrupted_dataset = dcmread("corrupted_dataset.dcm") #Works
print(pydicom.__version__) #Works
print(len(corrupted_dataset)) #Works

print(corrupted_dataset[0x0025, 0x1007]) #TypeError
print(corrupted_dataset[0x0025, 0x1019]) #TypeError
print(corrupted_dataset[0x0025, 0x101A]) #TypeError
print(corrupted_dataset[0x0025, 0x101B]) #TypeError
```

**Your environment**
```bash
module       | version
------       | -------
platform     | Windows-10-10.0.19041-SP0
Python       | 3.9.0 (tags/v3.9.0:9cf6752, Oct  5 2020, 15:34:40) [MSC v.1927 64 bit (AMD64)]
pydicom      | 2.3.0
gdcm         | _module not found_
jpeg_ls      | _module not found_
numpy        | 1.22.3
PIL          | _module not found_
pylibjpeg    | _module not found_
openjpeg     | _module not found_
libjpeg      | _module not found_
```
[corrupted_dataset.zip](https://github.com/pydicom/pydicom/files/8537669/corrupted_dataset.zip)


Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/pydicom/pydicom/pull/1639?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pydicom) Report
> Merging [#1639](https://codecov.io/gh/pydicom/pydicom/pull/1639?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pydicom) (505e6a5) into [master](https://codecov.io/gh/pydicom/pydicom/commit/9aaaa24ad6b26fdc7e5ab2c35f2f3f3807955217?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pydicom) (9aaaa24) will **decrease** coverage by `9.51%`.
> The diff coverage is `100.00%`.

```diff
@@            Coverage Diff             @@
##           master    #1639      +/-   ##
==========================================
- Coverage   97.57%   88.05%   -9.52%     
==========================================
  Files          66       66              
  Lines       10666    10430     -236     
==========================================
- Hits        10407     9184    -1223     
- Misses        259     1246     +987     
```


| [Impacted Files](https://codecov.io/gh/pydicom/pydicom/pull/1639?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pydicom) | Coverage Δ | |
|---|---|---|
| [pydicom/dataelem.py](https://codecov.io/gh/pydicom/pydicom/pull/1639/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pydicom#diff-cHlkaWNvbS9kYXRhZWxlbS5weQ==) | `98.07% <ø> (-0.33%)` | :arrow_down: |
| [pydicom/datadict.py](https://codecov.io/gh/pydicom/pydicom/pull/1639/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pydicom#diff-cHlkaWNvbS9kYXRhZGljdC5weQ==) | `100.00% <100.00%> (ø)` | |
| [pydicom/pixel\_data\_handlers/util.py](https://codecov.io/gh/pydicom/pydicom/pull/1639/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pydicom#diff-cHlkaWNvbS9waXhlbF9kYXRhX2hhbmRsZXJzL3V0aWwucHk=) | `17.93% <0.00%> (-82.07%)` | :arrow_down: |
| [pydicom/waveforms/numpy\_handler.py](https://codecov.io/gh/pydicom/pydicom/pull/1639/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pydicom#diff-cHlkaWNvbS93YXZlZm9ybXMvbnVtcHlfaGFuZGxlci5weQ==) | `20.31% <0.00%> (-79.69%)` | :arrow_down: |
| [pydicom/encoders/gdcm.py](https://codecov.io/gh/pydicom/pydicom/pull/1639/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pydicom#diff-cHlkaWNvbS9lbmNvZGVycy9nZGNtLnB5) | `20.96% <0.00%> (-75.81%)` | :arrow_down: |
| [pydicom/pixel\_data\_handlers/numpy\_handler.py](https://codecov.io/gh/pydicom/pydicom/pull/1639/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pydicom#diff-cHlkaWNvbS9waXhlbF9kYXRhX2hhbmRsZXJzL251bXB5X2hhbmRsZXIucHk=) | `27.14% <0.00%> (-72.86%)` | :arrow_down: |
| [pydicom/pixel\_data\_handlers/gdcm\_handler.py](https://codecov.io/gh/pydicom/pydicom/pull/1639/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pydicom#diff-cHlkaWNvbS9waXhlbF9kYXRhX2hhbmRsZXJzL2dkY21faGFuZGxlci5weQ==) | `26.22% <0.00%> (-66.46%)` | :arrow_down: |
| [pydicom/overlays/numpy\_handler.py](https://codecov.io/gh/pydicom/pydicom/pull/1639/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pydicom#diff-cHlkaWNvbS9vdmVybGF5cy9udW1weV9oYW5kbGVyLnB5) | `35.18% <0.00%> (-64.82%)` | :arrow_down: |
| [pydicom/pixel\_data\_handlers/pillow\_handler.py](https://codecov.io/gh/pydicom/pydicom/pull/1639/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pydicom#diff-cHlkaWNvbS9waXhlbF9kYXRhX2hhbmRsZXJzL3BpbGxvd19oYW5kbGVyLnB5) | `31.73% <0.00%> (-64.43%)` | :arrow_down: |
| [pydicom/pixel\_data\_handlers/rle\_handler.py](https://codecov.io/gh/pydicom/pydicom/pull/1639/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pydicom#diff-cHlkaWNvbS9waXhlbF9kYXRhX2hhbmRsZXJzL3JsZV9oYW5kbGVyLnB5) | `36.44% <0.00%> (-63.56%)` | :arrow_down: |
| ... and [19 more](https://codecov.io/gh/pydicom/pydicom/pull/1639/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pydicom) | |

:mega: Codecov can now indicate which changes are the most critical in Pull Requests. [Learn more](https://about.codecov.io/product/feature/runtime-insights/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pydicom)


Patch:
diff --git a/doc/release_notes/v2.4.0.rst b/doc/release_notes/v2.4.0.rst
index bb50bf9b2..1361ec317 100644
--- a/doc/release_notes/v2.4.0.rst
+++ b/doc/release_notes/v2.4.0.rst
@@ -10,4 +10,4 @@ Fixes
 * Fixed length validation of DS values with maximum length without a leading
   zero (:issue:`1632`)
 * Increased download speed with progress bar for test data (:issue:`1611`)
-
+* Fixed crash due to invalid private creator (:issue:`1638`)
diff --git a/pydicom/datadict.py b/pydicom/datadict.py
index 0457ed91a..ae6898b03 100644
--- a/pydicom/datadict.py
+++ b/pydicom/datadict.py
@@ -1,7 +1,7 @@
 # Copyright 2008-2018 pydicom authors. See LICENSE file for details.
 # -*- coding: utf-8 -*-
 """Access dicom dictionary information"""
-
+import warnings
 from typing import Tuple, Optional, Dict
 
 from pydicom.config import logger
@@ -553,6 +553,11 @@ def get_private_entry(
             f"Private creator '{private_creator}' not in the private "
             "dictionary"
         ) from exc
+    except TypeError as exc:
+        msg = (f"{tag.private_creator} '{private_creator}' "
+               f"is not a valid private creator")
+        warnings.warn(msg)
+        raise KeyError(msg) from exc
 
     # private elements are usually agnostic for
     # "block" (see PS3.5-2008 7.8.1 p44)
@@ -634,7 +639,7 @@ def private_dictionary_description(tag: TagType, private_creator: str) -> str:
         The tag for the element whose description is being retrieved, in any
         of the forms accepted by :func:`~pydicom.tag.Tag`.
     private_creator : str
-        The name of the private createor.
+        The name of the private creator.
 
     Returns
     -------
@@ -644,6 +649,7 @@ def private_dictionary_description(tag: TagType, private_creator: str) -> str:
     Raises
     ------
     KeyError
-        If the tag is not present in the private dictionary.
+        If the tag is not present in the private dictionary,
+        or if the private creator is not valid.
     """
     return get_private_entry(tag, private_creator)[2]
diff --git a/pydicom/dataelem.py b/pydicom/dataelem.py
index 992972cc0..9beb06b2d 100644
--- a/pydicom/dataelem.py
+++ b/pydicom/dataelem.py
@@ -650,9 +650,9 @@ class DataElement:
         if self.tag.is_private:
             if self.private_creator:
                 try:
-                    # If have name from private dictionary, use it, but
-                    #   but put in square brackets so is differentiated,
-                    #   and clear that cannot access it by name
+                    # If we have the name from the private dictionary, use it,
+                    # but put it in square brackets to make clear
+                    # that the tag cannot be accessed by that name
                     name = private_dictionary_description(
                         self.tag, self.private_creator
                     )
diff --git a/pydicom/tag.py b/pydicom/tag.py
index da74c0374..1972ffad8 100644
--- a/pydicom/tag.py
+++ b/pydicom/tag.py
@@ -226,6 +226,15 @@ class BaseTag(int):
         """
         return self.is_private and 0x0010 <= self.element < 0x0100
 
+    @property
+    def private_creator(self) -> "BaseTag":
+        """Return the private creator tag for the given tag.
+        The result is meaningless if this is not a private tag.
+
+        .. versionadded:: 2.4
+        """
+        return BaseTag((self & 0xffff0000) | self.element >> 8)
+
 
 def TupleTag(group_elem: Tuple[int, int]) -> BaseTag:
     """Fast factory for :class:`BaseTag` object with known safe (group, elem)


Test patch:
diff --git a/pydicom/tests/test_dataset.py b/pydicom/tests/test_dataset.py
index 1e487ce21..c54341020 100644
--- a/pydicom/tests/test_dataset.py
+++ b/pydicom/tests/test_dataset.py
@@ -1245,6 +1245,21 @@ class TestDataset:
         assert ds[tag2].value == b'\x03\x04'
         assert ds[tag2].VR == 'UN'
 
+    def test_invalid_private_creator(self):
+        # regression test for #1638
+        ds = Dataset()
+        ds.add_new(0x00250010, "SL", [13975, 13802])
+        ds.add_new(0x00250011, "LO", "Valid Creator")
+        ds.add_new(0x00251007, "UN", "foobar")
+        ds.add_new(0x00251107, "UN", "foobaz")
+        msg = (r"\(0025, 0010\) '\[13975, 13802]' "
+               r"is not a valid private creator")
+        with pytest.warns(UserWarning, match=msg):
+            assert (str(ds[0x00251007]) == "(0025, 1007) Private tag data"
+                                           "                    UN: 'foobar'")
+        assert (str(ds[0x00251107]) == "(0025, 1107) Private tag data"
+                                       "                    UN: 'foobaz'")
+
     def test_is_original_encoding(self):
         """Test Dataset.write_like_original"""
         ds = Dataset()
diff --git a/pydicom/tests/test_tag.py b/pydicom/tests/test_tag.py
index 7936c12b2..3a8933cdc 100644
--- a/pydicom/tests/test_tag.py
+++ b/pydicom/tests/test_tag.py
@@ -226,7 +226,7 @@ class TestBaseTag:
         # Group 0 not private
         assert not BaseTag(0x00000001).is_private
 
-    def test_private_creator(self):
+    def test_is_private_creator(self):
         """Test BaseTag.is_private_creator returns correct values."""
         # Non-private tag
         assert not BaseTag(0x00080010).is_private_creator
@@ -236,6 +236,10 @@ class TestBaseTag:
         assert BaseTag(0x000900FF).is_private_creator
         assert not BaseTag(0x00090100).is_private_creator
 
+    def test_private_creator(self):
+        assert BaseTag(0x00091000).private_creator == BaseTag(0x00090010)
+        assert BaseTag(0x00292526).private_creator == BaseTag(0x00290025)
+
 
 class TestTag:
     """Test the Tag method."""