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 | NoneWhich 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 by PEP 649 – Deferred Evaluation Of Annotations Using Descriptors, and it's coming "mandatory" in Python 3.14.)
There are a few aspects to consider: Which type-hints can you use, which pitfalls are there between options, the answer to which you should is opinion-based, however there is at least some guidance.
ruohola in the other answer here already gives an overview over this. Most importantly:
| can be used
from __future__ import annotationsFor a type-checker these three are equivalent: Optional[ListNode], Union[ListNode, None], None | ListNode as well as "ListNode | None" as string.
Under the hood there are some differences: | creates a types.UnionType, contrary Optional and Union create a subtype of typing._GenericAlias, and a string is a string.
If you do not care about runtime behavior you can skip these two paragraphs.
Nice to Know:
The most crucial difference is that with from __future__ import annotations | PEP 563 resp. PEP 649 the annotations will be strings instead of python objects. Strings have the advantage that you actually do not need to import modules into your namespace, which can save you runtime by moving not-runtime-relevant imports to a if TYPE_CHECKING: import ...type-checking-block
Similarly you can most of the time omit quotation marks when working with type-hints that otherwise would raise a NameError during runtime.
Advanced:
The __annotations__ attribute behaves differently depending on the version & the usage of from __future__ import annotations.
Please prefer typing(_extensions).get_type_hints instead of looking directly at __annotations__ and expect that you might get typing.ForwardRef("YourType") instead of a concrete type.
ListNode | None is better readable than Union[ListNode| None]"ListNode" | None is invalid at runtime; everything must be a string "ListNode | None"Optional[bool] and bool | None might have a tiny bit of different semantics for some people, e.g. I would not use the former for a required parameter.| instead of Unions| None instead of OptionalNone should be last| "a_string" pitfallTC001 - TC004If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With