Skip to content

Commit 6439d0d

Browse files
authored
Merge pull request #1186 from what-in-the-nim/reduce-peak-memory
Reduce peak memory on import by 62x
2 parents 2d1466a + f91a977 commit 6439d0d

20 files changed

Lines changed: 133 additions & 73 deletions

File tree

docs/api/spell.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,9 @@ The `NorvigSpellChecker` class is a fundamental component of the `pythainlp.spel
4848
DEFAULT_SPELL_CHECKER
4949
~~~~~~~~~~~~~~~~~~~~~
5050
.. autodata:: DEFAULT_SPELL_CHECKER
51-
:annotation: = Default instance of the standard NorvigSpellChecker, using word list data from the Thai National Corpus: http://www.arts.chula.ac.th/ling/tnc/
51+
:annotation: = Default reference of the standard NorvigSpellChecker, using word list data from the Thai National Corpus: http://www.arts.chula.ac.th/ling/tnc/
5252

53-
The `DEFAULT_SPELL_CHECKER` is an instance of the `NorvigSpellChecker` class with default settings. It is pre-configured to use word list data from the Thai National Corpus, making it a reliable choice for general spell-checking tasks.
53+
The `DEFAULT_SPELL_CHECKER` is an reference to the `NorvigSpellChecker` class with default settings. It is pre-configured to use word list data from the Thai National Corpus, making it a reliable choice for general spell-checking tasks.
5454

5555
References
5656
----------

pythainlp/augment/word2vec/thai2fit.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from pythainlp.augment.word2vec.core import Word2VecAug
88
from pythainlp.corpus import get_corpus_path
9-
from pythainlp.tokenize import THAI2FIT_TOKENIZER
9+
from pythainlp.tokenize import thai2fit_tokenizer
1010

1111

1212
class Thai2fitAug:
@@ -26,7 +26,8 @@ def tokenizer(self, text: str) -> List[str]:
2626
:param str text: Thai text
2727
:rtype: List[str]
2828
"""
29-
return THAI2FIT_TOKENIZER.word_tokenize(text)
29+
tok = thai2fit_tokenizer()
30+
return tok.word_tokenize(text)
3031

3132
def load_w2v(self):
3233
"""

pythainlp/spell/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
from pythainlp.spell.pn import NorvigSpellChecker
2020

21-
DEFAULT_SPELL_CHECKER = NorvigSpellChecker()
21+
DEFAULT_SPELL_CHECKER = NorvigSpellChecker
2222

2323
# these imports are placed here to avoid circular imports
2424
from pythainlp.spell.core import correct, correct_sent, spell, spell_sent

pythainlp/spell/core.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,16 @@
66
Spell checking functions
77
"""
88

9+
from functools import lru_cache
910
import itertools
1011
from typing import List
1112

1213
from pythainlp.spell import DEFAULT_SPELL_CHECKER
1314

15+
@lru_cache
16+
def default_spell_checker():
17+
"""Lazy load default spell checker with cache"""
18+
return DEFAULT_SPELL_CHECKER()
1419

1520
def spell(word: str, engine: str = "pn") -> List[str]:
1621
"""
@@ -72,7 +77,7 @@ def spell(word: str, engine: str = "pn") -> List[str]:
7277

7378
text_correct = SPELL_CHECKER(word)
7479
else:
75-
text_correct = DEFAULT_SPELL_CHECKER.spell(word)
80+
text_correct = default_spell_checker().spell(word)
7681

7782
return text_correct
7883

@@ -125,7 +130,7 @@ def correct(word: str, engine: str = "pn") -> str:
125130
text_correct = SPELL_CHECKER(word)
126131

127132
else:
128-
text_correct = DEFAULT_SPELL_CHECKER.correct(word)
133+
text_correct = default_spell_checker().correct(word)
129134

130135
return text_correct
131136

pythainlp/tokenize/__init__.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"""
88

99
__all__ = [
10-
"THAI2FIT_TOKENIZER",
10+
"thai2fit_tokenizer",
1111
"Tokenizer",
1212
"Trie",
1313
"paragraph_tokenize",
@@ -19,6 +19,7 @@
1919
"display_cell_tokenize",
2020
]
2121

22+
from functools import lru_cache
2223
from pythainlp.corpus import thai_syllables, thai_words
2324
from pythainlp.util.trie import Trie
2425

@@ -27,9 +28,15 @@
2728
DEFAULT_SUBWORD_TOKENIZE_ENGINE = "tcc"
2829
DEFAULT_SYLLABLE_TOKENIZE_ENGINE = "han_solo"
2930

30-
DEFAULT_WORD_DICT_TRIE = Trie(thai_words())
31-
DEFAULT_SYLLABLE_DICT_TRIE = Trie(thai_syllables())
32-
DEFAULT_DICT_TRIE = DEFAULT_WORD_DICT_TRIE
31+
@lru_cache
32+
def word_dict_trie():
33+
"""Lazy load default word dict trie with cache"""
34+
return Trie(thai_words())
35+
36+
@lru_cache
37+
def syllable_dict_trie():
38+
"""Lazy load default syllable dict trie with cache"""
39+
return Trie(thai_syllables())
3340

3441
from pythainlp.tokenize.core import (
3542
Tokenizer,
@@ -41,9 +48,4 @@
4148
word_tokenize,
4249
display_cell_tokenize,
4350
)
44-
45-
from pythainlp.corpus import get_corpus as _get_corpus
46-
47-
THAI2FIT_TOKENIZER = Tokenizer(
48-
custom_dict=_get_corpus("words_th_thai2fit_201810.txt"), engine="mm"
49-
)
51+
from pythainlp.tokenize.thai2fit import thai2fit_tokenizer

pythainlp/tokenize/core.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@
88

99
import copy
1010
import re
11-
from typing import Iterable, List, Union
11+
from typing import Iterable, List, Optional, Union
1212

1313
from pythainlp.tokenize import (
1414
DEFAULT_SENT_TOKENIZE_ENGINE,
1515
DEFAULT_SUBWORD_TOKENIZE_ENGINE,
16-
DEFAULT_SYLLABLE_DICT_TRIE,
16+
syllable_dict_trie,
1717
DEFAULT_SYLLABLE_TOKENIZE_ENGINE,
18-
DEFAULT_WORD_DICT_TRIE,
18+
word_dict_trie,
1919
DEFAULT_WORD_TOKENIZE_ENGINE,
2020
)
2121
from pythainlp.tokenize._utils import (
@@ -97,7 +97,7 @@ def word_detokenize(
9797

9898
def word_tokenize(
9999
text: str,
100-
custom_dict: Trie = Trie([]),
100+
custom_dict: Optional[Trie] = None,
101101
engine: str = DEFAULT_WORD_TOKENIZE_ENGINE,
102102
keep_whitespace: bool = True,
103103
join_broken_num: bool = True,
@@ -223,6 +223,9 @@ def word_tokenize(
223223

224224
segments = []
225225

226+
if custom_dict is None:
227+
custom_dict = Trie([])
228+
226229
if custom_dict and engine in (
227230
"attacut",
228231
"icu",
@@ -690,7 +693,7 @@ def subword_tokenize(
690693
for word in words:
691694
segments.extend(
692695
word_tokenize(
693-
text=word, custom_dict=DEFAULT_SYLLABLE_DICT_TRIE
696+
text=word, custom_dict=syllable_dict_trie()
694697
)
695698
)
696699
elif engine == "ssg":
@@ -881,7 +884,7 @@ def __init__(
881884
if custom_dict:
882885
self.__trie_dict = dict_trie(custom_dict)
883886
else:
884-
self.__trie_dict = DEFAULT_WORD_DICT_TRIE
887+
self.__trie_dict = word_dict_trie()
885888
self.__engine = engine
886889
if self.__engine not in ["newmm", "mm", "longest", "deepcut"]:
887890
raise NotImplementedError(

pythainlp/tokenize/etcc.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,19 @@
1919
and backward longest matching techniques." In International Symposium on
2020
Communications and Information Technology (ISCIT), pp. 37-40. 2001.
2121
"""
22+
from functools import lru_cache
2223
import re
2324
from typing import List
2425

2526
from pythainlp import thai_follow_vowels
2627
from pythainlp.corpus import get_corpus
2728
from pythainlp.tokenize import Tokenizer
2829

29-
_cut_etcc = Tokenizer(get_corpus("etcc.txt"), engine="longest")
30+
@lru_cache
31+
def _cut_etcc():
32+
"""Lazy load ETCC tokenizer with cache"""
33+
return Tokenizer(get_corpus("etcc.txt"), engine="longest")
34+
3035
_PAT_ENDING_CHAR = f"[{thai_follow_vowels}ๆฯ]"
3136
_RE_ENDING_CHAR = re.compile(_PAT_ENDING_CHAR)
3237

@@ -64,4 +69,4 @@ def segment(text: str) -> List[str]:
6469
if not text or not isinstance(text, str):
6570
return []
6671

67-
return _cut_subword(_cut_etcc.word_tokenize(text))
72+
return _cut_subword(_cut_etcc().word_tokenize(text))

pythainlp/tokenize/longest.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@
1212
1313
"""
1414
import re
15-
from typing import Dict, List, Union
15+
from typing import Dict, List, Optional, Union
1616

1717
from pythainlp import thai_tonemarks
18-
from pythainlp.tokenize import DEFAULT_WORD_DICT_TRIE
18+
from pythainlp.tokenize import word_dict_trie
1919
from pythainlp.util import Trie
2020

2121
_FRONT_DEP_CHAR = [
@@ -152,7 +152,7 @@ def tokenize(self, text: str) -> List[str]:
152152
_tokenizers: Dict[int, LongestMatchTokenizer] = {}
153153

154154

155-
def segment(text: str, custom_dict: Trie = DEFAULT_WORD_DICT_TRIE) -> List[str]:
155+
def segment(text: str, custom_dict: Optional[Trie] = None) -> List[str]:
156156
"""
157157
Dictionary-based longest matching word segmentation.
158158
@@ -164,7 +164,7 @@ def segment(text: str, custom_dict: Trie = DEFAULT_WORD_DICT_TRIE) -> List[str]:
164164
return []
165165

166166
if not custom_dict:
167-
custom_dict = DEFAULT_WORD_DICT_TRIE
167+
custom_dict = word_dict_trie()
168168

169169
global _tokenizers
170170
custom_dict_ref_id = id(custom_dict)

pythainlp/tokenize/multi_cut.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@
1515

1616
import re
1717
from collections import defaultdict
18-
from typing import Iterator, List
18+
from typing import Iterator, List, Optional
1919

20-
from pythainlp.tokenize import DEFAULT_WORD_DICT_TRIE
20+
from pythainlp.tokenize import word_dict_trie
2121
from pythainlp.util import Trie
2222

2323

@@ -48,12 +48,11 @@ def __init__(self, value, multi=None, in_dict=True):
4848

4949

5050
def _multicut(
51-
text: str, custom_dict: Trie = DEFAULT_WORD_DICT_TRIE
51+
text: str, custom_dict: Optional[Trie] = None
5252
) -> Iterator[LatticeString]:
5353
"""Return LatticeString"""
5454
if not custom_dict:
55-
custom_dict = DEFAULT_WORD_DICT_TRIE
56-
55+
custom_dict = word_dict_trie()
5756
len_text = len(text)
5857
words_at = defaultdict(list) # main data structure
5958

@@ -123,40 +122,46 @@ def _combine(ww: List[LatticeString]) -> Iterator[str]:
123122

124123

125124
def segment(
126-
text: str, custom_dict: Trie = DEFAULT_WORD_DICT_TRIE
125+
text: str, custom_dict: Optional[Trie] = None
127126
) -> List[str]:
128127
"""Dictionary-based maximum matching word segmentation.
129128
130129
:param text: text to be tokenized
131130
:type text: str
132131
:param custom_dict: tokenization dictionary,\
133-
defaults to DEFAULT_WORD_DICT_TRIE
132+
defaults to a Trie generated from pythainlp.corpus.thai_words
134133
:type custom_dict: Trie, optional
135134
:return: list of segmented tokens
136135
:rtype: List[str]
137136
"""
138137
if not text or not isinstance(text, str):
139138
return []
140139

140+
if not custom_dict:
141+
custom_dict = word_dict_trie()
142+
141143
return list(_multicut(text, custom_dict=custom_dict))
142144

143145

144146
def find_all_segment(
145-
text: str, custom_dict: Trie = DEFAULT_WORD_DICT_TRIE
147+
text: str, custom_dict: Optional[Trie] = None
146148
) -> List[str]:
147149
"""Get all possible segment variations.
148150
149151
:param text: input string to be tokenized
150152
:type text: str
151153
:param custom_dict: tokenization dictionary,\
152-
defaults to DEFAULT_WORD_DICT_TRIE
154+
defaults to word_dict_trie()
153155
:type custom_dict: Trie, optional
154156
:return: list of segment variations
155157
:rtype: List[str]
156158
"""
157159
if not text or not isinstance(text, str):
158160
return []
159161

162+
if not custom_dict:
163+
custom_dict = word_dict_trie()
164+
160165
ww = list(_multicut(text, custom_dict=custom_dict))
161166

162167
return list(_combine(ww))

pythainlp/tokenize/newmm.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@
1818
import re
1919
from collections import defaultdict
2020
from heapq import heappop, heappush
21-
from typing import Generator, List
21+
from typing import Generator, List, Optional
2222

23-
from pythainlp.tokenize import DEFAULT_WORD_DICT_TRIE
23+
from pythainlp.tokenize import word_dict_trie
2424
from pythainlp.tokenize.tcc_p import tcc_pos
2525
from pythainlp.util import Trie
2626

@@ -140,7 +140,7 @@ def _onecut(text: str, custom_dict: Trie) -> Generator[str, None, None]:
140140

141141
def segment(
142142
text: str,
143-
custom_dict: Trie = DEFAULT_WORD_DICT_TRIE,
143+
custom_dict: Optional[Trie] = None,
144144
safe_mode: bool = False,
145145
) -> List[str]:
146146
"""Maximal-matching word segmentation constrained by Thai Character Cluster.
@@ -153,7 +153,7 @@ def segment(
153153
:param text: text to be tokenized
154154
:type text: str
155155
:param custom_dict: tokenization dictionary,\
156-
defaults to DEFAULT_WORD_DICT_TRIE
156+
defaults to word_dict_trie()
157157
:type custom_dict: Trie, optional
158158
:param safe_mode: reduce chance for long processing time for long text\
159159
with many ambiguous breaking points, defaults to False
@@ -165,7 +165,7 @@ def segment(
165165
return []
166166

167167
if not custom_dict:
168-
custom_dict = DEFAULT_WORD_DICT_TRIE
168+
custom_dict = word_dict_trie()
169169

170170
if not safe_mode or len(text) < _TEXT_SCAN_END:
171171
return list(_onecut(text, custom_dict))

0 commit comments

Comments
 (0)