|
3 | 3 | # SPDX-License-Identifier: Apache-2.0 |
4 | 4 | from __future__ import annotations |
5 | 5 |
|
| 6 | +from importlib.resources import as_file, files |
| 7 | + |
6 | 8 | from pycrfsuite import Tagger as CRFTagger |
7 | 9 |
|
8 | | -from pythainlp.corpus import path_pythainlp_corpus, thai_stopwords |
| 10 | +from pythainlp.corpus import thai_stopwords |
9 | 11 |
|
10 | 12 |
|
11 | 13 | def _is_stopword(word: str) -> bool: # check Thai stopword |
@@ -55,16 +57,60 @@ def extract_features(doc): |
55 | 57 |
|
56 | 58 |
|
57 | 59 | class CRFchunk: |
| 60 | + """CRF-based chunker for Thai text. |
| 61 | +
|
| 62 | + This class can be used as a context manager to ensure proper cleanup |
| 63 | + of resources. Example: |
| 64 | +
|
| 65 | + with CRFchunk() as chunker: |
| 66 | + result = chunker.parse(tokens) |
| 67 | +
|
| 68 | + Alternatively, the object will attempt to clean up resources when |
| 69 | + garbage collected, though this is not guaranteed. |
| 70 | + """ |
| 71 | + |
58 | 72 | def __init__(self, corpus: str = "orchidpp"): |
59 | 73 | self.corpus = corpus |
| 74 | + self._model_file_ctx = None |
60 | 75 | self.load_model(self.corpus) |
61 | 76 |
|
62 | 77 | def load_model(self, corpus: str): |
63 | 78 | self.tagger = CRFTagger() |
64 | 79 | if corpus == "orchidpp": |
65 | | - self.path = path_pythainlp_corpus("crfchunk_orchidpp.model") |
66 | | - self.tagger.open(self.path) |
| 80 | + corpus_files = files("pythainlp.corpus") |
| 81 | + model_file = corpus_files.joinpath("crfchunk_orchidpp.model") |
| 82 | + self._model_file_ctx = as_file(model_file) |
| 83 | + model_path = self._model_file_ctx.__enter__() |
| 84 | + self.tagger.open(str(model_path)) |
67 | 85 |
|
68 | 86 | def parse(self, token_pos: list[tuple[str, str]]) -> list[str]: |
69 | 87 | self.xseq = extract_features(token_pos) |
70 | 88 | return self.tagger.tag(self.xseq) |
| 89 | + |
| 90 | + def __enter__(self): |
| 91 | + """Context manager entry.""" |
| 92 | + return self |
| 93 | + |
| 94 | + def __exit__(self, exc_type, exc_val, exc_tb): |
| 95 | + """Context manager exit - clean up resources.""" |
| 96 | + if self._model_file_ctx is not None: |
| 97 | + try: |
| 98 | + self._model_file_ctx.__exit__(exc_type, exc_val, exc_tb) |
| 99 | + self._model_file_ctx = None |
| 100 | + except Exception: # noqa: S110 |
| 101 | + pass |
| 102 | + return False |
| 103 | + |
| 104 | + def __del__(self): |
| 105 | + """Clean up the context manager when object is destroyed. |
| 106 | +
|
| 107 | + Note: __del__ is not guaranteed to be called and should not be |
| 108 | + relied upon for critical cleanup. Use the context manager protocol |
| 109 | + (with statement) for reliable resource management. |
| 110 | + """ |
| 111 | + if self._model_file_ctx is not None: |
| 112 | + try: |
| 113 | + self._model_file_ctx.__exit__(None, None, None) |
| 114 | + except Exception: # noqa: S110 |
| 115 | + # Silently ignore cleanup errors during garbage collection |
| 116 | + pass |
0 commit comments