Skip to content

Commit c3e39ed

Browse files
authored
Merge pull request #1263 from PyThaiNLP/copilot/add-type-hints-to-tokenize
Add 100% type hints to pythainlp.tokenize submodule
2 parents 8c0f684 + e4e46cd commit c3e39ed

15 files changed

Lines changed: 132 additions & 97 deletions

File tree

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,6 @@ module = [
411411
"khamyo.*",
412412
"khanaa.*",
413413
"multiel.*",
414-
"nlpo3.*",
415414
"nltk.*",
416415
"numpy.*",
417416
"onnxruntime.*",

pythainlp/tokenize/_utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@
77
from __future__ import annotations
88

99
import re
10-
from collections.abc import Callable
10+
from collections.abc import Callable, Sequence
1111

1212
_DIGITS_WITH_SEPARATOR = re.compile(r"(\d+[\.\,:])+\d+")
1313

1414

1515
def apply_postprocessors(
16-
segments: list[str], postprocessors: list[Callable[[list[str]], list[str]]]
16+
segments: list[str], postprocessors: Sequence[Callable[[list[str]], list[str]]]
1717
) -> list[str]:
1818
"""A list of callables to apply to a raw segmentation result.
1919
"""

pythainlp/tokenize/attacut.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from __future__ import annotations
1111

1212
import threading
13+
from typing import cast
1314

1415
from attacut import Tokenizer
1516

@@ -24,7 +25,7 @@ def __init__(self, model="attacut-sc"):
2425
self._tokenizer = Tokenizer(model=self._MODEL_NAME)
2526

2627
def tokenize(self, text: str) -> list[str]:
27-
return self._tokenizer.tokenize(text)
28+
return cast(list[str], self._tokenizer.tokenize(text))
2829

2930

3031
_tokenizers: dict[str, AttacutTokenizer] = {}

pythainlp/tokenize/budoux.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from __future__ import annotations
1414

1515
import threading
16+
from typing import cast
1617

1718
_parser = None
1819
_parser_lock = threading.Lock()
@@ -55,6 +56,6 @@ def segment(text: str) -> list[str]:
5556
_parser = _init_parser()
5657
parser = _parser
5758

58-
result = parser.parse(text)
59+
result = cast(list[str], parser.parse(text))
5960

6061
return result

pythainlp/tokenize/core.py

Lines changed: 54 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232

3333
def word_detokenize(
3434
segments: Union[list[list[str]], list[str]], output: str = "str"
35-
) -> Union[list[str], str]:
35+
) -> Union[list[list[str]], str]:
3636
"""Word detokenizer.
3737
3838
Detokenizes the list of words in each sentence into text.
@@ -49,18 +49,18 @@ def word_detokenize(
4949
print(word_detokenize(["เรา", "เล่น"]))
5050
# output: เราเล่น
5151
"""
52-
list_all = []
52+
list_all: list[list[str]] = []
5353

5454
if isinstance(segments[0], str):
55-
segments = [segments]
55+
segments = [segments] # type: ignore[assignment]
5656

5757
from pythainlp import thai_characters
5858

5959
for i, s in enumerate(segments):
60-
list_sents = []
61-
add_index = []
62-
space_index = []
63-
mark_index = []
60+
list_sents: list[str] = []
61+
add_index: list[int] = []
62+
space_index: list[int] = []
63+
mark_index: list[int] = []
6464
for j, w in enumerate(s):
6565
if j > 0:
6666
# previous word
@@ -92,9 +92,9 @@ def word_detokenize(
9292
if output == "list":
9393
return list_all
9494

95-
text = []
96-
for i in list_all:
97-
text.append("".join(i))
95+
text: list[str] = []
96+
for sent_tokens in list_all:
97+
text.append("".join(sent_tokens))
9898
return " ".join(text)
9999

100100

@@ -259,56 +259,56 @@ def word_tokenize(
259259

260260
segments = segment(text, custom_dict, safe_mode=True)
261261
elif engine == "attacut":
262-
from pythainlp.tokenize.attacut import segment
262+
from pythainlp.tokenize.attacut import segment as attacut_segment # noqa: I001
263263

264-
segments = segment(text)
264+
segments = attacut_segment(text)
265265
elif engine == "longest":
266-
from pythainlp.tokenize.longest import segment
266+
from pythainlp.tokenize.longest import segment as longest_segment # noqa: I001
267267

268-
segments = segment(text, custom_dict)
268+
segments = longest_segment(text, custom_dict)
269269
elif engine in ("mm", "multi_cut"):
270-
from pythainlp.tokenize.multi_cut import segment
270+
from pythainlp.tokenize.multi_cut import segment as multi_cut_segment # noqa: I001
271271

272-
segments = segment(text, custom_dict)
272+
segments = multi_cut_segment(text, custom_dict)
273273
elif engine == "deepcut": # deepcut can optionally use dictionary
274-
from pythainlp.tokenize.deepcut import segment
274+
from pythainlp.tokenize.deepcut import segment as deepcut_segment # noqa: I001
275275

276276
if custom_dict:
277-
custom_dict = list(custom_dict)
278-
segments = segment(text, custom_dict)
277+
custom_dict = list(custom_dict) # type: ignore[assignment]
278+
segments = deepcut_segment(text, custom_dict)
279279
else:
280-
segments = segment(text)
280+
segments = deepcut_segment(text)
281281
elif engine == "icu":
282-
from pythainlp.tokenize.pyicu import segment
282+
from pythainlp.tokenize.pyicu import segment as pyicu_segment # noqa: I001
283283

284-
segments = segment(text)
284+
segments = pyicu_segment(text)
285285
elif engine == "budoux":
286-
from pythainlp.tokenize.budoux import segment
286+
from pythainlp.tokenize.budoux import segment as budoux_segment # noqa: I001
287287

288-
segments = segment(text)
288+
segments = budoux_segment(text)
289289
elif engine == "nercut":
290-
from pythainlp.tokenize.nercut import segment
290+
from pythainlp.tokenize.nercut import segment as nercut_segment # noqa: I001
291291

292-
segments = segment(text)
292+
segments = nercut_segment(text)
293293
elif engine == "sefr_cut":
294-
from pythainlp.tokenize.sefr_cut import segment
294+
from pythainlp.tokenize.sefr_cut import segment as sefrcut_segment # noqa: I001
295295

296-
segments = segment(text)
296+
segments = sefrcut_segment(text)
297297
elif engine == "tltk":
298-
from pythainlp.tokenize.tltk import segment
298+
from pythainlp.tokenize.tltk import segment as tltk_segment # noqa: I001
299299

300-
segments = segment(text)
300+
segments = tltk_segment(text)
301301
elif engine == "oskut":
302-
from pythainlp.tokenize.oskut import segment
302+
from pythainlp.tokenize.oskut import segment as oskut_segment # noqa: I001
303303

304-
segments = segment(text)
304+
segments = oskut_segment(text)
305305
elif engine == "nlpo3":
306-
from pythainlp.tokenize.nlpo3 import segment
306+
from pythainlp.tokenize.nlpo3 import segment as nlpo3_segment # noqa: I001
307307

308308
# Currently cannot handle custom_dict from inside word_tokenize(),
309309
# due to difference in type.
310310
# if isinstance(custom_dict, str):
311-
# segments = segment(text, custom_dict=custom_dict)
311+
# segments = nlpo3_segment(text, custom_dict=custom_dict)
312312
# elif not isinstance(custom_dict, str) and not custom_dict:
313313
# raise ValueError(
314314
# f"""Tokenizer \"{engine}\":
@@ -317,8 +317,8 @@ def word_tokenize(
317317
# See pythainlp.tokenize.nlpo3.load_dict()"""
318318
# )
319319
# else:
320-
# segments = segment(text)
321-
segments = segment(text)
320+
# segments = nlpo3_segment(text)
321+
segments = nlpo3_segment(text)
322322
else:
323323
raise ValueError(
324324
f"""Tokenizer \"{engine}\" not found.
@@ -413,7 +413,7 @@ def sent_tokenize(
413413
text: Union[str, list[str]],
414414
engine: str = DEFAULT_SENT_TOKENIZE_ENGINE,
415415
keep_whitespace: bool = True,
416-
) -> list[str]:
416+
) -> Union[list[str], list[list[str]]]:
417417
"""Sentence tokenizer.
418418
419419
Tokenizes running text into "sentences". Supports both string and list of strings.
@@ -632,7 +632,7 @@ def paragraph_tokenize(
632632
It might be a typo; if not, please consult our document."""
633633
)
634634

635-
return segments
635+
return segments # type: ignore[return-value]
636636

637637

638638
def subword_tokenize(
@@ -719,36 +719,41 @@ def subword_tokenize(
719719
segments = []
720720

721721
if engine == "tcc":
722-
from pythainlp.tokenize.tcc import segment
722+
from pythainlp.tokenize.tcc import segment as tcc_segment
723+
segments = tcc_segment(text)
723724
elif engine == "tcc_p":
724-
from pythainlp.tokenize.tcc_p import segment
725+
from pythainlp.tokenize.tcc_p import segment as tcc_p_segment
726+
segments = tcc_p_segment(text)
725727
elif engine == "etcc":
726-
from pythainlp.tokenize.etcc import segment
728+
from pythainlp.tokenize.etcc import segment as etcc_segment
729+
segments = etcc_segment(text)
727730
elif engine == "wangchanberta":
728-
from pythainlp.wangchanberta import segment
731+
from pythainlp.wangchanberta import segment as wangchanberta_segment
732+
segments = wangchanberta_segment(text)
729733
elif engine == "dict": # use syllable dictionary
730734
words = word_tokenize(text)
731735
for word in words:
732736
segments.extend(
733737
word_tokenize(text=word, custom_dict=syllable_dict_trie())
734738
)
735739
elif engine == "ssg":
736-
from pythainlp.tokenize.ssg import segment
740+
from pythainlp.tokenize.ssg import segment as ssg_segment
741+
segments = ssg_segment(text)
737742
elif engine == "tltk":
738-
from pythainlp.tokenize.tltk import syllable_tokenize as segment
743+
from pythainlp.tokenize.tltk import syllable_tokenize as tltk_segment
744+
segments = tltk_segment(text)
739745
elif engine == "han_solo":
740-
from pythainlp.tokenize.han_solo import segment
746+
from pythainlp.tokenize.han_solo import segment as han_solo_segment
747+
segments = han_solo_segment(text)
741748
elif engine == "phayathai":
742-
from pythainlp.phayathaibert import segment
749+
from pythainlp.phayathaibert import segment as phayathai_segment
750+
segments = phayathai_segment(text)
743751
else:
744752
raise ValueError(
745753
f"""Tokenizer \"{engine}\" not found.
746754
It might be a typo; if not, please consult our document."""
747755
)
748756

749-
if not segments:
750-
segments = segment(text)
751-
752757
if not keep_whitespace:
753758
segments = strip_whitespace(segments)
754759

pythainlp/tokenize/crfcut.py

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -183,10 +183,7 @@ def segment(text: str) -> list[str]:
183183
:param str text: text to be tokenized into sentences
184184
:return: list of words, tokenized from the text
185185
"""
186-
if isinstance(text, str):
187-
toks = word_tokenize(text)
188-
else:
189-
toks = text
186+
toks = word_tokenize(text)
190187
feat = extract_features(toks)
191188
labs = _tagger.tag(feat)
192189
labs[-1] = "E" # make sure it cuts the last sentence

pythainlp/tokenize/deepcut.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
from __future__ import annotations
1414

15-
from typing import Union
15+
from typing import Union, cast
1616

1717
try:
1818
from deepcut import tokenize
@@ -29,6 +29,6 @@ def segment(text: str, custom_dict: Union[Trie, list[str], str] = []) -> list[st
2929
if isinstance(custom_dict, Trie):
3030
custom_dict = list(custom_dict)
3131

32-
return tokenize(text, custom_dict)
32+
return cast(list[str], tokenize(text, custom_dict))
3333

34-
return tokenize(text)
34+
return cast(list[str], tokenize(text))

pythainlp/tokenize/longest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ def __longest_matching(self, text: str, begin_pos: int) -> str:
106106
else:
107107
return ""
108108

109-
def __segment(self, text: str):
109+
def __segment(self, text: str) -> list[str]:
110110
begin_pos = 0
111111
len_text = len(text)
112112
tokens: list[str] = []

pythainlp/tokenize/nercut.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ def segment(
4040
:param class tagger: NER tagger engine
4141
:return: list of words, tokenized from the text
4242
"""
43-
if not isinstance(text, str):
43+
if not text:
4444
return []
4545

4646
tagged_words = tagger.tag(text, pos=False)
@@ -73,7 +73,5 @@ def segment(
7373
words.append(combining_word)
7474
elif curr_tag.startswith("I-") and combining_word != "":
7575
words.append(combining_word)
76-
else:
77-
pass
7876

7977
return words

pythainlp/tokenize/nlpo3.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66
import threading
77
from importlib.resources import as_file, files
88
from sys import stderr
9+
from typing import TYPE_CHECKING
910

10-
from nlpo3 import load_dict as nlpo3_load_dict
11-
from nlpo3 import segment as nlpo3_segment
11+
if TYPE_CHECKING:
12+
from nlpo3 import load_dict as nlpo3_load_dict # noqa: F401
13+
from nlpo3 import segment as nlpo3_segment # noqa: F401
1214

1315
from pythainlp.corpus.common import _THAI_WORDS_FILENAME
1416

@@ -25,6 +27,13 @@ def _ensure_default_dict_loaded():
2527
The context manager is kept alive for the lifetime of the program
2628
to prevent cleanup of temporary files while the dictionary is in use.
2729
"""
30+
try:
31+
from nlpo3 import load_dict as nlpo3_load_dict
32+
except ImportError as ex:
33+
raise ImportError(
34+
"nlpo3 is not installed. Install it with: pip install nlpo3"
35+
) from ex
36+
2837
global _NLPO3_DEFAULT_DICT, _dict_file_ctx
2938
if _NLPO3_DEFAULT_DICT is None:
3039
with _load_lock:
@@ -57,6 +66,13 @@ def load_dict(file_path: str, dict_name: str) -> bool:
5766
* \
5867
https://github.com/PyThaiNLP/nlpo3
5968
"""
69+
try:
70+
from nlpo3 import load_dict as nlpo3_load_dict
71+
except ImportError as ex:
72+
raise ImportError(
73+
"nlpo3 is not installed. Install it with: pip install nlpo3"
74+
) from ex
75+
6076
msg, success = nlpo3_load_dict(file_path=file_path, dict_name=dict_name)
6177
if not success:
6278
print(msg, file=stderr)
@@ -87,6 +103,13 @@ def segment(
87103
* \
88104
https://github.com/PyThaiNLP/nlpo3
89105
"""
106+
try:
107+
from nlpo3 import segment as nlpo3_segment
108+
except ImportError as ex:
109+
raise ImportError(
110+
"nlpo3 is not installed. Install it with: pip install nlpo3"
111+
) from ex
112+
90113
# Ensure default dict is loaded if it's being used
91114
if custom_dict == _NLPO3_DEFAULT_DICT_NAME:
92115
_ensure_default_dict_loaded()

0 commit comments

Comments
 (0)