It seems we can use the following types for hinting LinkedLists in Python:
Union[ListNode, None]
or Union['ListNode', None]
Optional[ListNode]
Optional['ListNode']
ListNode | None
Which of these should we prefer?
Feel free to share any other relevant insights as well.
Here we simply want to merge K sorted LinkedLists:
from typing import Union, List, Any, Optional
import unittest
import gc
class ListNode:
def __init__(self, val: int = 0, next: Union['ListNode', None] = None):
self.val = val
self.next = next
class Solution:
def mergeKLists(self, linkedlists: Optional[List[ListNode]]) -> Union[ListNode, None]:
if not linkedlists:
return None
interval = 1
while interval < len(linkedlists):
for i in range(0, len(linkedlists) - interval, interval << 1):
linkedlists[i] = self.merge_two_linkedlists(linkedlists[i], linkedlists[i + interval])
interval <<= 1
return linkedlists[0]
def merge_two_linkedlists(self, l1: Union[ListNode, None], l2: Union[ListNode, None]) -> Union[ListNode, None]:
sentinel = curr = ListNode()
while l1 and l2:
if l1.val <= l2.val:
curr.next = l1
l1 = l1.next
else:
curr.next = l2
l2 = l2.next
curr = curr.next
if l1:
curr.next = l1
else:
curr.next = l2
return sentinel.next
def main_test_merge_k_linkedlists(test_case_class):
suite = unittest.TestLoader().loadTestsFromTestCase(test_case_class)
runner = unittest.TextTestRunner(verbosity=2, failfast=1)
return runner.run(suite)
class TestMergeKLists(unittest.TestCase):
def test_merge_k_sorted_linkedlists(self):
l1 = ListNode(10)
l1.next = ListNode(20)
l2 = ListNode(40)
l2.next = ListNode(50)
l3 = ListNode(80)
l3.next = ListNode(150)
self.assertEqual(Solution().mergeKLists([l1, l2, l3]), l1)
l1 = l2 = l3 = None
gc.collect()
if __name__ == '__main__':
main_test_merge_k_linkedlists(TestMergeKLists)
typing.Optional[T]
is just shorthand for typing.Union[T, None]
. Would always prefer the former to the latter for its succinctness. Union
is of course still useful when using it with something else than None
.
After Python 3.10, a union of types can simply be written with the |
operator (Optional[T]
becomes T | None
). So nowadays it's unnecessary to import/use either Union
or Optional
.
Also, as of Python 3.9, the built-in collection types support the subscripting []
operator, so a generic typing.List[T]
can now just be written as list[T]
.
The reason for sometimes needing to have a type written in quotes, is that if the type does not exist yet (e.g. using the class as a type within its own body), the string format can still be understood by type checkers and it doesn't cause a runtime NameError
.
This stringification need can be avoided by using the from __future__ import annotations
statement at the top of a module. That statement explicitly opts you into PEP 563 – Postponed Evaluation of Annotations. (That PEP has actually been superseded PEP 649 – Deferred Evaluation Of Annotations Using Descriptors, and it's coming "mandatory" in Python 3.14.)