diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 06ca900c..91373be4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-versions: [ '3.7', '3.8', '3.9', '3.10' ] + python-versions: [ '3.9', '3.10', '3.11.2' ] #python-versions: [ '2.7', '3.7', '3.8', '3.9', '3.10', '3.11' ] steps: diff --git a/LICENSE b/LICENSE index 8174bda9..2eec3041 100644 --- a/LICENSE +++ b/LICENSE @@ -14,7 +14,7 @@ copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/data.py b/data.py index 3cb64f39..2fbc9eb8 100755 --- a/data.py +++ b/data.py @@ -1,8 +1,4 @@ -# coding: utf-8 - -from __future__ import print_function, unicode_literals -import codecs, subprocess, random -import multiprocessing +import subprocess, random from collections import Counter from itertools import islice from nltk.tag import untag @@ -19,16 +15,14 @@ def create_words_file(dic_file="resources/persian.dic", output="hazm/data/words. dic_words = [ line.strip().replace(", ", ",").split("\t") - for line in codecs.open(dic_file, encoding="utf-8") + for line in open(dic_file, encoding="utf-8") if len(line.strip().split("\t")) == 3 ] - dic_words = filter( - lambda item: not item[2].startswith("V") and "NEG" not in item[2], dic_words - ) + dic_words = [item for item in dic_words if not item[2].startswith("V") and "NEG" not in item[2]] dic_words = [ "\t".join(item) for item in sorted(dic_words, key=lambda item: item[0]) ] - print(*dic_words, sep="\n", file=codecs.open(output, "w", "utf-8")) + print(*dic_words, sep="\n", file=open(output, "w", "utf-8")) print(output, "created") @@ -38,7 +32,7 @@ def evaluate_lemmatizer( lemmatizer = Lemmatizer() errors = [] - with codecs.open("resources/lemmatizer_errors.txt", "w", "utf8") as output: + with open("resources/lemmatizer_errors.txt", "w", "utf8") as output: dadegan = DadeganReader(conll_file) for tree in dadegan.trees(): for node in tree.nodelist[1:]: @@ -47,11 +41,11 @@ def evaluate_lemmatizer( errors.append((word, lemma, pos, lemmatizer.lemmatize(word, pos))) print(len(errors), "errors", file=output) counter = Counter(errors) - for item, count in sorted(counter.items(), key=lambda t: t[1], reverse=True): + for item, count in sorted(list(counter.items()), key=lambda t: t[1], reverse=True): print(count, *item, file=output) missed = [] - with codecs.open("resources/lemmatizer_missed.txt", "w", "utf8") as output: + with open("resources/lemmatizer_missed.txt", "w", "utf8") as output: peykare = PeykareReader(peykare_root) for sentence in peykare.sents(): for word in sentence: @@ -60,7 +54,7 @@ def evaluate_lemmatizer( missed.append(word[0]) print(len(missed), "missed", file=output) counter = Counter(missed) - for item, count in sorted(counter.items(), key=lambda t: t[1], reverse=True): + for item, count in sorted(list(counter.items()), key=lambda t: t[1], reverse=True): print(count, item, file=output) @@ -81,7 +75,7 @@ def evaluate_normalizer(tnews_root="corpora/tnews"): affix_spacing=False, ) - with codecs.open("resources/normalized.txt", "w", "utf8") as output1, codecs.open( + with open("resources/normalized.txt", "w", "utf8") as output1, open( "resources/normalized_token_based.txt", "w", "utf8" ) as output2: random.seed(0) @@ -99,7 +93,7 @@ def evaluate_informal_normalizer(sentipars_root="corpora/sentipers"): normalizer = Normalizer() informal_normalizer = InformalNormalizer() - output = codecs.open("resources/normalized.txt", "w", "utf8") + output = open("resources/normalized.txt", "w", "utf8") for comments in sentipers.comments(): for comment in comments: for sentence in comment: @@ -120,7 +114,7 @@ def evaluate_chunker(treebank_root="corpora/treebank"): print(chunker.evaluate(chunked_trees)) - output = codecs.open("resources/chunker_errors.txt", "w", "utf8") + output = open("resources/chunker_errors.txt", "w", "utf8") for sentence, gold in zip(treebank.sents(), chunked_trees): chunked = chunker.parse(sentence) if chunked != gold: @@ -155,14 +149,14 @@ def train_postagger( '*:s1=%m[0,0,".?$"]', '*:s2=%m[0,0,".?.?$"]', '*:s3=%m[0,0,".?.?.?$"]', - '*:p?l=%t[-1,0,"\p"]', - '*:p?=%t[0,0,"\p"]', - '*:p?r=%t[1,0,"\p"]', - '*:p?a=%t[0,0,"^\p*$"]', - '*:n?l=%t[-1,0,"\d"]', - '*:n?=%t[0,0,"\d"]', - '*:n?r=%t[1,0,"\d"]', - '*:n?a=%t[0,0,"^\d*$"]', + r'*:p?l=%t[-1,0,"\p"]', + r'*:p?=%t[0,0,"\p"]', + r'*:p?r=%t[1,0,"\p"]', + r'*:p?a=%t[0,0,"^\p*$"]', + r'*:n?l=%t[-1,0,"\d"]', + r'*:n?=%t[0,0,"\d"]', + r'*:n?r=%t[1,0,"\d"]', + r'*:n?a=%t[0,0,"^\d*$"]', ], ) @@ -204,7 +198,7 @@ def train_chunker( ) def retag_trees(trees, sents): - for tree, sentence in zip(trees, tagger.tag_sents(map(untag, sents))): + for tree, sentence in zip(trees, tagger.tag_sents(list(map(untag, sents)))): for n, word in zip(tree.treepositions("leaves"), sentence): tree[n] = word @@ -234,9 +228,9 @@ def train_maltparser( train, test = DadeganReader(train_file), DadeganReader(test_file) train_data = train_file + ".data" - with codecs.open(train_data, "w", "utf8") as output: + with open(train_data, "w", "utf8") as output: for tree, sentence in zip( - train.trees(), tagger.tag_sents(map(untag, train.sents())) + train.trees(), tagger.tag_sents(list(map(untag, train.sents()))) ): for i, (node, word) in enumerate( zip(list(tree.nodes.values())[1:], sentence), start=1 @@ -283,16 +277,16 @@ def train_maltparser( # evaluation parser = MaltParser(tagger=tagger, lemmatizer=lemmatizer, model_file=model_file) - parsed_trees = parser.parse_sents(map(untag, test.sents())) + parsed_trees = parser.parse_sents(list(map(untag, test.sents()))) test_data, test_results = test_file + ".data", test_file + ".results" print( "\n".join([tree.to_conll(10) for tree in test.trees()]).strip(), - file=codecs.open(test_data, "w", "utf8"), + file=open(test_data, "w", "utf8"), ) print( "\n".join([tree.to_conll(10) for tree in parsed_trees]).strip(), - file=codecs.open(test_results, "w", "utf8"), + file=open(test_results, "w", "utf8"), ) subprocess.Popen( ["java", "-jar", "resources/MaltEval.jar", "-g", test_data, "-s", test_results] @@ -309,9 +303,9 @@ def train_turboparser( train, test = DadeganReader(train_file), DadeganReader(test_file) train_data = train_file + ".data" - with codecs.open(train_data, "w", "utf8") as output: + with open(train_data, "w", "utf8") as output: for tree, sentence in zip( - train.trees(), tagger.tag_sents(map(untag, train.sents())) + train.trees(), tagger.tag_sents(list(map(untag, train.sents()))) ): for i, (node, word) in enumerate( zip(list(tree.nodes.values())[1:], sentence), start=1 @@ -346,16 +340,16 @@ def train_turboparser( # evaluation parser = TurboParser(tagger=tagger, lemmatizer=lemmatizer, model_file=model_file) - parsed_trees = parser.parse_sents(map(untag, test.sents())) + parsed_trees = parser.parse_sents(list(map(untag, test.sents()))) test_data, test_results = test_file + ".data", test_file + ".results" print( "\n".join([tree.to_conll(10) for tree in test.trees()]).strip(), - file=codecs.open(test_data, "w", "utf8"), + file=open(test_data, "w", "utf8"), ) print( "\n".join([tree.to_conll(10) for tree in parsed_trees]).strip(), - file=codecs.open(test_results, "w", "utf8"), + file=open(test_results, "w", "utf8"), ) subprocess.Popen( [ @@ -390,9 +384,9 @@ def train_stanford_postagger( list(peykare.sents()), test_size=test_size, random_state=0 ) - output = codecs.open(train_file, "w", "utf8") + output = open(train_file, "w", "utf8") for sentence in train: - print(*(map(lambda w: "/".join(w).replace(" ", "_"), sentence)), file=output) + print(*(["/".join(w).replace(" ", "_") for w in sentence]), file=output) subprocess.Popen( [ "java", diff --git a/format_docstrings.py b/format_docstrings.py index ba4cc627..5b1db6c1 100644 --- a/format_docstrings.py +++ b/format_docstrings.py @@ -1,8 +1,8 @@ import re, textwrap, glob -def format_all_docstrings(pyFile): - text = open(pyFile, "r", encoding="utf-8").read() +def format_all_docstrings(py_file): + text = open(py_file, encoding="utf-8").read() text = text.replace("\t", " ") # Regex pattern that matches all docstrings @@ -13,7 +13,7 @@ def format_all_docstrings(pyFile): new_doc = format_docstring(old_doc) text = text.replace(old_doc, new_doc) - open(pyFile, "w", encoding="utf-8").write(text) + open(py_file, "w", encoding="utf-8").write(text) def format_section(section, new): @@ -38,7 +38,7 @@ def wrap_text(text, width): result = "" lines = text.split("\n") for line in lines: - wrapped_line = textwrap.fill(line, 79) + wrapped_line = textwrap.fill(line, width) result += wrapped_line + "\n" return result diff --git a/hazm/BijankhanReader.py b/hazm/BijankhanReader.py index 9f2c200f..e3cdfd2c 100644 --- a/hazm/BijankhanReader.py +++ b/hazm/BijankhanReader.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """این ماژول شامل کلاس‌ها و توابعی برای خواندن پیکرهٔ بی‌جن‌خان است. [پیکرهٔ @@ -11,8 +9,7 @@ """ -from __future__ import unicode_literals -import re, codecs + from .Normalizer import * from .PeykareReader import join_verb_parts @@ -64,27 +61,29 @@ class BijankhanReader: """این کلاس شامل توابعی برای خواندن پیکرهٔ بی‌جن‌خان است. Args: - bijankhan_file (str): مسیر فایلِ پیکره. - joined_verb_parts (bool, optional): اگر `True‍` باشد افعال چندبخشی را با _ به‌هم می‌چسباند. - pos_map (str, optional): دیکشنری مبدل برچسب‌های ریز به درشت. + bijankhan_file: مسیر فایلِ پیکره. + joined_verb_parts: اگر `True‍` باشد افعال چندبخشی را با _ به‌هم می‌چسباند. + pos_map: دیکشنری مبدل برچسب‌های ریز به درشت. """ - def __init__(self, bijankhan_file, joined_verb_parts=True, pos_map=default_pos_map): + def __init__(self, bijankhan_file: str, joined_verb_parts:bool=True, pos_map:str=None): + if pos_map is None: + pos_map = default_pos_map self._bijankhan_file = bijankhan_file self._joined_verb_parts = joined_verb_parts self._pos_map = pos_map self._normalizer = Normalizer(correct_spacing=False) - def _sentences(self): + def _sentences(self) -> str: """جملات پیکره را به شکل متن خام برمی‌گرداند. Yields: - (str): جملهٔ بعدی. + جملهٔ بعدی. """ sentence = [] - for line in codecs.open(self._bijankhan_file, encoding="utf-8"): + for line in open(self._bijankhan_file, encoding="utf-8"): parts = re.split(" +", line.strip()) if len(parts) == 2: word, tag = parts @@ -96,7 +95,7 @@ def _sentences(self): yield sentence sentence = [] - def sents(self): + def sents(self) -> list[tuple[str,str]]: """جملات پیکره را به شکل لیستی از `(توکن،برچسب)`ها برمی‌گرداند.. Examples: @@ -105,7 +104,7 @@ def sents(self): [('اولین', 'ADJ'), ('سیاره', 'N'), ('خارج', 'ADJ'), ('از', 'PREP'), ('منظومه', 'N'), ('شمسی', 'ADJ'), ('دیده_شد', 'V'), ('.', 'PUNC')] Yields: - (List[Tuple[str,str]]): جملهٔ بعدی در قالب لیستی از `(توکن،برچسب)`ها. + جملهٔ بعدی در قالب لیستی از `(توکن،برچسب)`ها. """ map_poses = lambda item: (item[0], self._pos_map.get(item[1], item[1])) diff --git a/hazm/Chunker.py b/hazm/Chunker.py index f2b466e0..dcbe226f 100755 --- a/hazm/Chunker.py +++ b/hazm/Chunker.py @@ -1,18 +1,17 @@ -# coding: utf-8 - """این ماژول شامل کلاس‌ها و توابعی برای تجزیهٔ متن به عبارات اسمی، فعلی و حرف اضافه‌ای است. **میزان دقت تجزیه‌گر سطحی در نسخهٔ حاضر ۸۹.۹ درصد [^1] است.** [^1]: این عدد با انتشار هر نسخه بروزرسانی می‌شود. """ +from typing import Iterator -from __future__ import unicode_literals +import nltk.chunk.util from nltk.chunk import ChunkParserI, RegexpParser, tree2conlltags, conlltags2tree from .SequenceTagger import IOBTagger -def tree2brackets(tree): +def tree2brackets(tree:str) -> str: """خروجی درختی تابع [parse()][hazm.Chunker.Chunker.parse] را به یک ساختار کروشه‌ای تبدیل می‌کند. @@ -29,10 +28,10 @@ def tree2brackets(tree): '[نامه ایشان NP] [را POSTP] [دریافت داشتم VP] .' Args: - tree (str): ساختار درختی حاصل از پردزاش تابع parse() + tree: ساختار درختی حاصل از پردزاش تابع parse(). Returns: - (str): رشته‌ای از کروشه‌ها که در هر کروشه جزئی از متن به همراه نوع آن جای گرفته است. + رشته‌ای از کروشه‌ها که در هر کروشه جزئی از متن به همراه نوع آن جای گرفته است. """ str, tag = "", "" @@ -57,16 +56,16 @@ class Chunker(IOBTagger, ChunkParserI): """ - def train(self, trees): + def train(self, trees: list[str]): """از روی درخت ورودی، مدل را آموزش می‌دهد. Args: - trees (List[Tree]): لیستی از درخت‌ها برای آموزش مدل. + trees: لیستی از درخت‌ها برای آموزش مدل. """ - super(Chunker, self).train(map(tree2conlltags, trees)) + super().train(list(map(tree2conlltags, trees))) - def parse(self, sentence): + def parse(self, sentence: list[tuple[str, str]]) -> str: """جمله‌ای را در قالب لیستی از تاپل‌های دوتایی [(توکن, نوع), (توکن, نوع), ...] دریافت می‌کند و درخت تقطع‌شدهٔ آن را بر می‌گرداند. @@ -81,37 +80,37 @@ def parse(self, sentence): ./PUNC) Args: - sentence (List[Tuple[str,str]): جمله‌ای که باید درخت تقطیع‌شدهٔ آن تولید شود. + sentence: جمله‌ای که باید درخت تقطیع‌شدهٔ آن تولید شود. Returns: - (str): ساختار درختی حاصل از تقطیع. + ساختار درختی حاصل از تقطیع. برای تبدیل این ساختار درختی به یک ساختار کروشه‌ای و قابل‌درک‌تر می‌توانید از تابع `tree2brackets()` استفاده کنید. """ return next(self.parse_sents([sentence])) - def parse_sents(self, sentences): + def parse_sents(self, sentences: list[list[tuple[str,str]]]) -> Iterator[str]: """جملات ورودی را به‌شکل تقطیع‌شده و در قالب یک برمی‌گرداند. Args: - sentences (List[List[Tuple(str,str)]]): جملات ورودی. + جملات ورودی. Yields: - (Iterator[str]): یک `Iterator` از جملات تقطیع شده. + یک `Iterator` از جملات تقطیع شده. """ - for conlltagged in super(Chunker, self).tag_sents(sentences): + for conlltagged in super().tag_sents(sentences): yield conlltags2tree(conlltagged) - def evaluate(self, gold): + def evaluate(self, gold: list[str]) -> nltk.chunk.util.ChunkScore: """دقت مدل را ارزیابی می‌کند. Args: - gold (List[Tree]): دادهٔ مرجع برای ارزیابی دقت مدل. + gold: دادهٔ مرجع برای ارزیابی دقت مدل. Returns: - (ChunkScore): دقت تشخیص. + دقت تشخیص. """ return ChunkParserI.evaluate(self, gold) @@ -158,4 +157,4 @@ def __init__(self): """ - super(RuleBasedChunker, self).__init__(grammar=grammar) + super().__init__(grammar=grammar) diff --git a/hazm/DadeganReader.py b/hazm/DadeganReader.py index 7a9f2a49..2b581b7e 100755 --- a/hazm/DadeganReader.py +++ b/hazm/DadeganReader.py @@ -1,18 +1,15 @@ -# coding: utf-8 - """این ماژول شامل کلاس‌ها و توابعی برای خواندن پیکرهٔ PerDT است. PerDT حاوی تعداد قابل‌توجهی جملۀ برچسب‌خورده با اطلاعات نحوی و ساخت‌واژی است. """ +from typing import Iterator -from __future__ import unicode_literals -import codecs from nltk.parse import DependencyGraph from nltk.tree import Tree -def coarse_pos_u(tags, word): +def coarse_pos_u(tags, word: str) -> str: """برچسب‌های ریز را به برچسب‌های درشت منطبق با استاندارد جهانی (coarse-grained universal pos tags) تبدیل می‌کند. @@ -49,7 +46,7 @@ def coarse_pos_u(tags, word): return pos_mapped -def coarse_pos_e(tags, word): +def coarse_pos_e(tags, word: str) -> str: """برچسب‌های ریز را به برچسب‌های درشت (coarse-grained pos tags) تبدیل می‌کند. Examples: @@ -75,22 +72,22 @@ def coarse_pos_e(tags, word): return map.get(tags[0], "X") + ("e" if "EZ" in tags else "") -word_nodes = lambda tree: sorted(tree.nodes.values(), key=lambda node: node["address"])[ +word_nodes = lambda tree: sorted(list(tree.nodes.values()), key=lambda node: node["address"])[ 1: ] -node_deps = lambda node: sum(node["deps"].values(), []) +node_deps = lambda node: sum(list(node["deps"].values()), []) class DadeganReader: """این کلاس شامل توابعی برای خواندن پیکرهٔ PerDT است. Args: - conll_file(str): مسیر فایلِ پیکره. - pos_map(str,optionl): دیکشنری مبدل برچسب‌های ریز به درشت. + conll_file: مسیر فایلِ پیکره. + pos_map: دیکشنری مبدل برچسب‌های ریز به درشت. """ - def __init__(self, conll_file, pos_map=coarse_pos_e, universal_pos=False): + def __init__(self, conll_file: str, pos_map:str=coarse_pos_e, universal_pos:bool=False): self._conll_file = conll_file if pos_map is None: self._pos_map = lambda tags: ",".join(tags) @@ -99,14 +96,14 @@ def __init__(self, conll_file, pos_map=coarse_pos_e, universal_pos=False): else: self._pos_map = coarse_pos_e - def _sentences(self): + def _sentences(self) -> Iterator[str]: """جملات پیکره را به شکل متن خام برمی‌گرداند. Yields: - (str): جملهٔ بعدی. + جملهٔ بعدی. """ - with codecs.open(self._conll_file, encoding="utf8") as conll_file: + with open(self._conll_file, encoding="utf8") as conll_file: text = conll_file.read() # refine text @@ -124,11 +121,11 @@ def _sentences(self): if item.strip(): yield item - def trees(self): + def trees(self) -> Iterator[str]: """ساختار درختی جملات را برمی‌گرداند. Yields: - (str): ساختار درختی جملهٔ بعدی. + ساختار درختی جملهٔ بعدی. """ for sentence in self._sentences(): @@ -144,7 +141,7 @@ def trees(self): yield tree - def sents(self): + def sents(self) -> list[tuple[str,str]]: """لیستی از جملات را برمی‌گرداند. هر جمله لیستی از `(توکن، برچسب)`ها است. @@ -155,13 +152,13 @@ def sents(self): [('این', 'DET'), ('میهمانی', 'N'), ('به', 'P'), ('منظور', 'Ne'), ('آشنایی', 'Ne'), ('هم‌تیمی‌های', 'Ne'), ('او', 'PRO'), ('با', 'P'), ('غذاهای', 'Ne'), ('ایرانی', 'AJ'), ('ترتیب', 'N'), ('داده_شد', 'V'), ('.', 'PUNC')] Yields: - (List[Tuple[str,str]]): جملهٔ بعدی. + جملهٔ بعدی. """ for tree in self.trees(): yield [(node["word"], node["mtag"]) for node in word_nodes(tree)] - def chunked_trees(self): + def chunked_trees(self) -> str: """درخت وابستگی‌های جملات را برمی‌گرداند. Examples: @@ -171,7 +168,7 @@ def chunked_trees(self): '[این میهمانی NP] [به PP] [منظور آشنایی هم‌تیمی‌های او NP] [با PP] [غذاهای ایرانی NP] [ترتیب داده_شد VP] .' Yields: - (str): درخت وابستگی‌های جملهٔ بعدی. + درخت وابستگی‌های جملهٔ بعدی. """ for tree in self.trees(): diff --git a/hazm/DegarbayanReader.py b/hazm/DegarbayanReader.py index aa5ab0aa..10a8274f 100644 --- a/hazm/DegarbayanReader.py +++ b/hazm/DegarbayanReader.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """این ماژول شامل کلاس‌ها و توابعی برای خواندن پیکرهٔ دِگَربیان است. [پیکرهٔ @@ -14,8 +12,10 @@ """ -from __future__ import unicode_literals, print_function + import os +import sys +from typing import Iterator from xml.dom import minidom @@ -23,19 +23,19 @@ class DegarbayanReader: """این کلاس شامل توابعی برای خواندن پیکرهٔ دگربیان است. Args: - root (str): مسیر فولدر حاوی فایل‌های پیکره - corpus_file (str, optional): فایل اطلاعات پیکره. + root مسیر فولدر حاوی فایل‌های پیکره + corpus_file: فایل اطلاعات پیکره. در صورتی که بخواهید از حالت استاندارد پیکره استفاده کنید نیازی به تغییرِ این فایل نیست. - judge_type (str, optional): این پارامتر دارای دو مقدار `three_class` و `two_class` است. + judge_type: این پارامتر دارای دو مقدار `three_class` و `two_class` است. در حالت `three_class` جملات سه برچسب می‌خورند: ۱. `Paraphrase`(دگربیان) ۲. `SemiParaphrase`(تقریباً دگربیان) ۳. `NotParaphrase`(غیر دگربیان). در حالت `two_class` حالت دوم یعنی `SemiParaphrase` هم برچسب `Paraphrase` می‌خورَد. - version (float, optional): شمارهٔ نسخهٔ پیکره + version: شمارهٔ نسخهٔ پیکره """ def __init__( - self, root, corpus_file="CorpusPair.xml", judge_type="three_class", version=1.0 + self, root:str, corpus_file:str="CorpusPair.xml", judge_type:str="three_class", version:float=1.0 ): self._root = root self._corpus_file = corpus_file @@ -43,11 +43,11 @@ def __init__( if judge_type != "three_class" and judge_type != "two_class": self._judge_type = "three_class" - def docs(self): + def docs(self) -> Iterator[dict[str, str]]: """اسناد موجود در پیکره را برمی‌گرداند. Yields: - (Dict): سند بعدی. + سند بعدی. """ @@ -64,52 +64,43 @@ def judge_number_to_text(judge): try: elements = minidom.parse(filename) for element in elements.getElementsByTagName("Pair"): - pair = {} - pair["id"] = ( + pair = {"id": ( element.getElementsByTagName("PairId")[0] .childNodes[0] .data.strip() - ) - pair["news_source1"] = ( + ), "news_source1": ( element.getElementsByTagName("NewsSource1")[0] .childNodes[0] .data.strip() - ) - pair["news_source2"] = ( + ), "news_source2": ( element.getElementsByTagName("NewsSource2")[0] .childNodes[0] .data.strip() - ) - pair["news_id1"] = ( + ), "news_id1": ( element.getElementsByTagName("NewsId1")[0] .childNodes[0] .data.strip() - ) - pair["news_id2"] = ( + ), "news_id2": ( element.getElementsByTagName("NewsId2")[0] .childNodes[0] .data.strip() - ) - pair["sentence1"] = ( + ), "sentence1": ( element.getElementsByTagName("Sentence1")[0] .childNodes[0] .data.strip() - ) - pair["sentence2"] = ( + ), "sentence2": ( element.getElementsByTagName("Sentence2")[0] .childNodes[0] .data.strip() - ) - pair["method_type"] = ( + ), "method_type": ( element.getElementsByTagName("MethodType")[0] .childNodes[0] .data.strip() - ) - pair["judge"] = judge_number_to_text( + ), "judge": judge_number_to_text( element.getElementsByTagName("judge")[0] .childNodes[0] .data.strip() - ) + )} yield pair except Exception as e: @@ -118,7 +109,7 @@ def judge_number_to_text(judge): print("error in reading file", filename, e, file=sys.stderr) raise FileNotFoundError("error in reading file", filename) - def pairs(self): + def pairs(self) -> Iterator[tuple[str, str, str]]: """متن‌های دگربیان را در قالب یک `(متن اصلی، شکل دگربیان، برچسب)` برمی‌گرداند. Examples: @@ -127,7 +118,7 @@ def pairs(self): ('24 نفر نهایی تیم ملی بدون تغییری خاص معرفی شد', 'کی روش 24 بازیکن را به تیم ملی فوتبال دعوت کرد', 'Paraphrase') Yields: - (Tuple(str,str,str)): `متن دگربیان بعدی در قالب یک `(متن اصلی، شکل دگربیان، برچسب). + `متن دگربیان بعدی در قالب یک `(متن اصلی، شکل دگربیان، برچسب). """ for pair in self.docs(): diff --git a/hazm/DependencyParser.py b/hazm/DependencyParser.py index 32e382e4..4494ae67 100644 --- a/hazm/DependencyParser.py +++ b/hazm/DependencyParser.py @@ -1,11 +1,9 @@ -# coding: utf-8 - """این ماژول شامل کلاس‌ها و توابعی برای شناساییِ وابستگی‌های دستوری متن است. """ -from __future__ import print_function, unicode_literals -import os, codecs, tempfile + +import os, tempfile from nltk.parse import DependencyGraph from nltk.parse.api import ParserI from nltk.parse.malt import MaltParser @@ -15,15 +13,15 @@ class MaltParser(MaltParser): """این کلاس شامل توابعی برای شناسایی وابستگی‌های دستوری است. Args: - tagger (str): نام تابع `POS Tagger`. - lemmatizer (str): نام کلاس ریشه‌یاب. - working_dir (str, optional): محل ذخیره‌سازی `maltparser‍`. - model_file (str, optional): آدرس مدلِ از پیش آموزش دیده با پسوند `mco`. + tagger: نام تابع `POS Tagger`. + lemmatizer: نام کلاس ریشه‌یاب. + working_dir: محل ذخیره‌سازی `maltparser‍`. + model_file: آدرس مدلِ از پیش آموزش دیده با پسوند `mco`. """ def __init__( - self, tagger, lemmatizer, working_dir="resources", model_file="langModel.mco" + self, tagger: str, lemmatizer: str, working_dir:str="resources", model_file:str="langModel.mco" ): self.tagger = tagger self.working_dir = working_dir @@ -31,29 +29,29 @@ def __init__( self._malt_bin = os.path.join(working_dir, "malt.jar") self.lemmatize = lemmatizer.lemmatize if lemmatizer else lambda w, t: "_" - def parse_sents(self, sentences, verbose=False): + def parse_sents(self, sentences:str, verbose:bool=False) -> str: """گراف وابستگی را برمی‌گرداند. Args: - sentences (str): جملاتی که باید گراف وابستگی آن‌ها استخراج شود. - verbose (bool, optional): اگر `True` باشد وابستگی‌های بیشتری را برمی‌گرداند. + sentences: جملاتی که باید گراف وابستگی آن‌ها استخراج شود. + verbose: اگر `True` باشد وابستگی‌های بیشتری را برمی‌گرداند. Returns: - (str): گراف وابستگی. + گراف وابستگی. """ tagged_sentences = self.tagger.tag_sents(sentences) return self.parse_tagged_sents(tagged_sentences, verbose) - def parse_tagged_sents(self, sentences, verbose=False): + def parse_tagged_sents(self, sentences:str, verbose:bool=False) -> str: """گراف وابستگی‌ها را برای جملات ورودی برمی‌گرداند. Args: - sentences (str): جملاتی که باید گراف وابستگی‌های آن استخراج شود. - verbose (bool, optional): اگر `True` باشد وابستگی‌های بیشتری را برمی‌گرداند.. + sentences: جملاتی که باید گراف وابستگی‌های آن استخراج شود. + verbose: اگر `True` باشد وابستگی‌های بیشتری را برمی‌گرداند.. Returns: - (str): گراف وابستگی جملات. + گراف وابستگی جملات. Raises: Exception: در صورت بروز خطا یک اکسپشن عمومی صادر می‌شود. @@ -91,7 +89,7 @@ def parse_tagged_sents(self, sentences, verbose=False): ) ).encode("utf8") ) - input_file.write("\n\n".encode("utf8")) + input_file.write(b"\n\n") input_file.close() cmd = [ @@ -114,7 +112,7 @@ def parse_tagged_sents(self, sentences, verbose=False): return ( DependencyGraph(item) - for item in codecs.open(output_file.name, encoding="utf8") + for item in open(output_file.name, encoding="utf8") .read() .split("\n\n") if item.strip() @@ -181,14 +179,14 @@ def tagged_parse_sents(self, sentences): ) ).encode("utf8") ) - input_file.write("\n".encode("utf8")) + input_file.write(b"\n") input_file.close() self.interface.parse(input_file.name, output_file.name) return ( DependencyGraph(item, cell_extractor=lambda cells: cells[1:8]) - for item in codecs.open(output_file.name, encoding="utf8") + for item in open(output_file.name, encoding="utf8") .read() .split("\n\n") if item.strip() diff --git a/hazm/Embedding.py b/hazm/Embedding.py index 64b2d703..07c421eb 100644 --- a/hazm/Embedding.py +++ b/hazm/Embedding.py @@ -1,8 +1,9 @@ -# coding: utf-8 """ این ماژول شامل کلاس‌ها و توابعی برای تبدیل کلمه یا متن به برداری از اعداد است. """ +import numpy + from . import word_tokenize, Normalizer import multiprocessing import warnings @@ -19,12 +20,12 @@ class WordEmbedding: """این کلاس شامل توابعی برای تبدیل کلمه به برداری از اعداد است. Args: - model_type (str): نوع امبدینگ که می‌تواند یکی از مقادیر ‍`fasttext`, `keyedvector`, `glove` باشد. - model_path (str, optional): مسیر فایل امبدینگ. + model_type: نوع امبدینگ که می‌تواند یکی از مقادیر ‍`fasttext`, `keyedvector`, `glove` باشد. + model_path: مسیر فایل امبدینگ. """ - def __init__(self, model_type, model_path=None): + def __init__(self, model_type: str, model_path: str=None): if model_type != supported_embeddings: raise KeyError( f'Model type "{model_type}" is not supported! Please choose from {supported_embeddings}' @@ -33,7 +34,7 @@ def __init__(self, model_type, model_path=None): if model_path: self.load_model(model_path) - def load_model(self, model_path): + def load_model(self, model_path: str): """فایل امبدینگ را بارگزاری می‌کند. Examples: @@ -41,7 +42,7 @@ def load_model(self, model_path): >>> wordEmbedding.load_model('resources/cc.fa.300.bin') Args: - model_path (str): مسیر فایل امبدینگ. + model_path: مسیر فایل امبدینگ. """ @@ -65,12 +66,12 @@ def load_model(self, model_path): def train( self, - dataset_path, - workers=multiprocessing.cpu_count() - 1, - vector_size=200, - epochs=10, - fasttext_type="skipgram", - dest_path=None, + dataset_path: str, + workers:int=multiprocessing.cpu_count() - 1, + vector_size:int=200, + epochs:int=10, + fasttext_type:str="skipgram", + dest_path:str=None, ): """یک فایل امبدینگ از نوع fasttext ترین می‌کند. @@ -79,12 +80,12 @@ def train( >>> wordEmbedding.train(dataset_path = 'dataset.txt', worker = 4, vector_size = 300, epochs = 30, fasttext_type = 'cbow', dest_path = 'fasttext_model') Args: - dataset_path (str): مسیر فایل متنی. - worker (int, optional): تعداد هسته درگیر برای ترین مدل. - vector_size (int, optional): طول وکتور خروجی به ازای هر کلمه. - epochs (int, optional): تعداد تکرار ترین بر روی کل دیتا. - fasttext_type (str, optional): نوع fasttext مورد نظر برای ترین که میتواند یکی از مقادیر skipgram یا cbow را داشته باشد. - dest_path (str, optional): مسیر مورد نظر برای ذخیره فایل امبدینگ. + dataset_path: مسیر فایل متنی. + worker: تعداد هسته درگیر برای ترین مدل. + vector_size: طول وکتور خروجی به ازای هر کلمه. + epochs: تعداد تکرار ترین بر روی کل دیتا. + fasttext_type نوع fasttext مورد نظر برای ترین که میتواند یکی از مقادیر skipgram یا cbow را داشته باشد. + dest_path: مسیر مورد نظر برای ذخیره فایل امبدینگ. """ @@ -123,7 +124,7 @@ def __getitem__(self, word): raise AttributeError("Model must not be None! Please load model first.") return self.model[word] - def doesnt_match(self, words): + def doesnt_match(self, words: list[str]) -> str: """لیستی از کلمات را دریافت می‌کند و کلمهٔ نامرتبط را برمی‌گرداند. Examples: @@ -134,10 +135,10 @@ def doesnt_match(self, words): 'ساعت' Args: - words (list[str]): لیست کلمات. + words: لیست کلمات. Returns: - (str): کلمهٔ نامرتبط. + کلمهٔ نامرتبط. """ @@ -145,7 +146,7 @@ def doesnt_match(self, words): raise AttributeError("Model must not be None! Please load model first.") return self.model.doesnt_match(words) - def similarity(self, word1, word2): + def similarity(self, word1: str, word2: str) -> float: """میزان شباهت دو کلمه را برمی‌گرداند. Examples: @@ -156,11 +157,11 @@ def similarity(self, word1, word2): 0.08837362 Args: - word1 (str): کلمهٔ اول - word2 (str): کلمهٔ دوم + word1: کلمهٔ اول + word2: کلمهٔ دوم Returns: - (float): میزان شباهت دو کلمه. + میزان شباهت دو کلمه. """ @@ -168,7 +169,7 @@ def similarity(self, word1, word2): raise AttributeError("Model must not be None! Please load model first.") return float(str(self.model.similarity(word1, word2))) - def get_vocab(self): + def get_vocab(self) -> list[str]: """لیستی از کلمات موجود در فایل امبدینگ را برمی‌گرداند. Examples: @@ -177,7 +178,7 @@ def get_vocab(self): ['،', 'در', '.', 'و', ...] Returns: - (list[str]): لیست کلمات موجود در فایل امبدینگ. + لیست کلمات موجود در فایل امبدینگ. """ @@ -185,7 +186,7 @@ def get_vocab(self): raise AttributeError("Model must not be None! Please load model first.") return self.model.index_to_key - def nearest_words(self, word, topn=5): + def nearest_words(self, word: str, topn:int=5) -> list[tuple[str, str]]: """کلمات مرتبط با یک واژه را به همراه میزان ارتباط آن برمی‌گرداند. Examples: @@ -194,11 +195,11 @@ def nearest_words(self, word, topn=5): [('ايران', 0.657148540019989'), (جمهوری', 0.6470394134521484'), (آمریکا', 0.635792076587677'), (اسلامی', 0.6354473233222961'), (کشور', 0.6339613795280457')] Args: - word (str): کلمه‌ای که می‌خواهید واژگان مرتبط با آن را بدانید. - topn (int): تعداد کلمات مرتبطی که می‌خواهید برگردانده شود. + word: کلمه‌ای که می‌خواهید واژگان مرتبط با آن را بدانید. + topn: تعداد کلمات مرتبطی که می‌خواهید برگردانده شود. Returns: - (list[tuple]): لیستی از تاپل‌های [`کلمهٔ مرتبط`, `میزان ارتباط`]. + لیستی از تاپل‌های [`کلمهٔ مرتبط`, `میزان ارتباط`]. """ @@ -206,7 +207,7 @@ def nearest_words(self, word, topn=5): raise AttributeError("Model must not be None! Please load model first.") return self.model.most_similar(word, topn=topn) - def get_normal_vector(self, word): + def get_normal_vector(self, word: str) -> numpy.ndarray: """بردار امبدینگ نرمالایزشدهٔ کلمه ورودی را برمی‌گرداند. Examples: @@ -215,10 +216,10 @@ def get_normal_vector(self, word): array([ 8.99544358e-03, 2.76231226e-02, -1.06164828e-01, ..., -9.45233554e-02, -7.59726465e-02, -8.96625668e-02], dtype=float32) Args: - word (str): کلمه‌ای که می‌خواهید بردار متناظر با آن را بدانید. + word: کلمه‌ای که می‌خواهید بردار متناظر با آن را بدانید. Returns: - (numpy.ndarray(float32)): لیست بردار نرمالایزشدهٔ مرتبط با کلمهٔ ورودی. + لیست بردار نرمالایزشدهٔ مرتبط با کلمهٔ ورودی. """ @@ -232,15 +233,15 @@ class SentEmbedding: """این کلاس شامل توابعی برای تبدیل جمله به برداری از اعداد است. Args: - model_path (str, optional): مسیر فایل امبدینگ. + model_path: مسیر فایل امبدینگ. """ - def __init__(self, model_path=None): + def __init__(self, model_path: str=None): if model_path: self.load_model(model_path) - def load_model(self, model_path): + def load_model(self, model_path: str): """فایل امبدینگ را بارگذاری می‌کند. Examples: @@ -248,7 +249,7 @@ def load_model(self, model_path): >>> sentEmbedding.load_model('sent2vec_model_path') Args: - model_path (str): مسیر فایل امبدینگ. + model_path: مسیر فایل امبدینگ. """ @@ -256,13 +257,13 @@ def load_model(self, model_path): def train( self, - dataset_path, - min_count=5, - workers=multiprocessing.cpu_count() - 1, - windows=5, - vector_size=300, - epochs=10, - dest_path=None, + dataset_path: str, + min_count: int=5, + workers: int=multiprocessing.cpu_count() - 1, + windows: int=5, + vector_size: int=300, + epochs: int=10, + dest_path:str=None, ): """یک فایل امبدینگ doc2vec ترین می‌کند. @@ -271,13 +272,13 @@ def train( >>> sentEmbedding.train(dataset_path = 'dataset.txt', min_count = 10, worker = 6, windows = 3, vector_size = 250, epochs = 35, dest_path = 'doc2vec_model') Args: - dataset_path (str): مسیر فایل متنی. - min_count (int, optional): مینیموم دفعات تکرار یک کلمه برای حضور آن در لیست کلمات امبدینگ. - worker (int, optional): تعداد هسته درگیر برای ترین مدل. - wondows (int, optional): طول پنجره برای لحاظ کلمات اطراف یک کلمه در ترین آن. - vector_size (int, optional): طول وکتور خروجی به ازای هر جمله. - epochs (int, optional): تعداد تکرار ترین بر روی کل دیتا. - dest_path (str, optional): مسیر مورد نظر برای ذخیره فایل امبدینگ. + dataset_path: مسیر فایل متنی. + min_count: مینیموم دفعات تکرار یک کلمه برای حضور آن در لیست کلمات امبدینگ. + worker: تعداد هسته درگیر برای ترین مدل. + wondows: طول پنجره برای لحاظ کلمات اطراف یک کلمه در ترین آن. + vector_size: طول وکتور خروجی به ازای هر جمله. + epochs: تعداد تکرار ترین بر روی کل دیتا. + dest_path: مسیر مورد نظر برای ذخیره فایل امبدینگ. """ workers = 1 if workers == 0 else workers @@ -301,7 +302,7 @@ def train( model.save(dest_path) print("Model saved.") - def __getitem__(self, sent): + def __getitem__(self, sent: str) -> numpy.ndarray: if not self.model: raise AttributeError("Model must not be None! Please load model first.") return self.get_sentence_vector(sent) @@ -315,10 +316,10 @@ def get_sentence_vector(self, sent): array([-0.28460968, 0.04566888, -0.00979532, ..., -0.4701098 , -0.3010612 , -0.18577948], dtype=float32) Args: - sent (str): جمله‌ای که می‌خواهید بردار امبیدنگ آن را دریافت کنید. + sent: جمله‌ای که می‌خواهید بردار امبیدنگ آن را دریافت کنید. Returns: - (numpy.ndarray(float32)): لیست بردار مرتبط با جملهٔ ورودی. + لیست بردار مرتبط با جملهٔ ورودی. """ @@ -328,7 +329,7 @@ def get_sentence_vector(self, sent): tokenized_sent = word_tokenize(sent) return self.model.infer_vector(tokenized_sent) - def similarity(self, sent1, sent2): + def similarity(self, sent1: str, sent2: str) -> float: """میزان شباهت دو جمله را برمی‌گرداند. Examples: @@ -339,11 +340,11 @@ def similarity(self, sent1, sent2): 0.2379288 Args: - sent1 (str): جملهٔ اول. - sent2 (str): جملهٔ دوم. + sent1: جملهٔ اول. + sent2: جملهٔ دوم. Returns: - (float): میزان شباهت دو جمله که عددی بین `0` و`1` است. + میزان شباهت دو جمله که عددی بین `0` و`1` است. """ diff --git a/hazm/HamshahriReader.py b/hazm/HamshahriReader.py index 93e0a823..01e4e365 100644 --- a/hazm/HamshahriReader.py +++ b/hazm/HamshahriReader.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """این ماژول شامل کلاس‌ها و توابعی برای خواندن پیکرهٔ همشهری است. [پیکرهٔ @@ -14,8 +12,9 @@ """ -from __future__ import print_function + import os, sys, re +from typing import Iterator from xml.dom import minidom @@ -23,14 +22,13 @@ class HamshahriReader: """این کلاس شامل توابعی برای خواندن پیکرهٔ همشهری است. Args: - root (str): مسیر فولدرِ حاوی فایل‌های پیکرهٔ همشهری. + root: مسیر فولدرِ حاوی فایل‌های پیکرهٔ همشهری. """ - def __init__(self, root): + def __init__(self, root: str): self._root = root - self._invalids = set( - [ + self._invalids = { "hamshahri.dtd", "HAM2-960622.xml", "HAM2-960630.xml", @@ -75,11 +73,10 @@ def __init__(self, root): "HAM2-050406.xml", "HAM2-050407.xml", "HAM2-050416.xml", - ] - ) + } self._paragraph_pattern = re.compile(r"(\n.{0,50})(?=\n)") - def docs(self): + def docs(self) -> Iterator[dict[str, str]]: """خبرها را برمی‌گرداند. هر خبر، شی‌ای متشکل از این پارامتر است: @@ -96,7 +93,7 @@ def docs(self): 'HAM2-750403-001' Yields: - (Dict): خبر بعدی. + خبر بعدی. """ @@ -108,13 +105,11 @@ def docs(self): try: elements = minidom.parse(os.path.join(root, name)) for element in elements.getElementsByTagName("DOC"): - doc = {} - doc["id"] = ( + doc = {"id": ( element.getElementsByTagName("DOCID")[0].childNodes[0].data - ) - doc["issue"] = ( + ), "issue": ( element.getElementsByTagName("ISSUE")[0].childNodes[0].data - ) + )} for cat in element.getElementsByTagName("CAT"): doc[ @@ -145,7 +140,7 @@ def docs(self): except Exception as e: print("error in reading", name, e, file=sys.stderr) - def texts(self): + def texts(self) -> Iterator[str]: """فقط متن خبرها را در قالب یک برمی‌گرداند. این تابع صرفاً برای راحتی بیشتر تهیه شده وگرنه با تابع @@ -153,7 +148,7 @@ def texts(self): پراپرتی `text` نیز می‌توانید همین کار را انجام دهید. Yields: - (str): متنِ خبر بعدی. + متنِ خبر بعدی. """ for doc in self.docs(): diff --git a/hazm/InformalNormalizer.py b/hazm/InformalNormalizer.py index 399630a1..5b5599aa 100644 --- a/hazm/InformalNormalizer.py +++ b/hazm/InformalNormalizer.py @@ -1,12 +1,10 @@ -# coding: utf-8 - """این ماژول شامل کلاس‌ها و توابعی برای نرمال‌سازی متن‌های محاوره‌ای است. """ -from __future__ import unicode_literals -import codecs -from .utils import informal_verbs, informal_words, NUMBERS, default_verbs + + +from .utils import informal_verbs, informal_words, NUMBERS from .Normalizer import Normalizer from .Lemmatizer import Lemmatizer from .Stemmer import Stemmer @@ -18,38 +16,38 @@ class InformalNormalizer(Normalizer): """این کلاس شامل توابعی برای نرمال‌سازی متن‌های محاوره‌ای است. Args: - verb_file (str, optional): فایل حاوی افعال محاوره‌ای. - word_file (str, optional): فایل حاوی کلمات محاوره‌ای. - seperation_flag (bool, optional): اگر `True` باشد و در بخشی از متن به فاصله نیاز بود آن فاصله درج می‌شود. + verb_file: فایل حاوی افعال محاوره‌ای. + word_file: فایل حاوی کلمات محاوره‌ای. + seperation_flag: اگر `True` باشد و در بخشی از متن به فاصله نیاز بود آن فاصله درج می‌شود. **kargs: پارامترهای نامدارِ اختیاری """ def __init__( self, - verb_file=informal_verbs, - word_file=informal_words, - seperation_flag=False, + verb_file:str=informal_verbs, + word_file:str=informal_words, + seperation_flag:bool=False, **kargs ): self.seperation_flag = seperation_flag self.lemmatizer = Lemmatizer() self.ilemmatizer = InformalLemmatizer() self.stemmer = Stemmer() - super(InformalNormalizer, self).__init__(**kargs) + super().__init__(**kargs) self.sent_tokenizer = SentenceTokenizer() self.word_tokenizer = WordTokenizer() - with codecs.open(verb_file, encoding="utf8") as vf: + with open(verb_file, encoding="utf8") as vf: self.pastVerbs = {} self.presentVerbs = {} - for f, i, flag in map(lambda x: x.strip().split(" ", 2), vf): + for f, i, flag in [x.strip().split(" ", 2) for x in vf]: splitedF = f.split("#") self.presentVerbs.update({i: splitedF[1]}) self.pastVerbs.update({splitedF[0]: splitedF[0]}) - with codecs.open(default_verbs, encoding="utf8") as vf: - for f, i in map(lambda x: x.strip().split("#", 2), vf): + with open(default_verbs, encoding="utf8") as vf: + for f, i in [x.strip().split("#", 2) for x in vf]: self.presentVerbs.update({i: i}) self.pastVerbs.update({f: f}) @@ -76,25 +74,25 @@ def informal_to_formal_conjucation(i, f, flag): return res - with codecs.open(verb_file, encoding="utf8") as vf: + with open(verb_file, encoding="utf8") as vf: self.iverb_map = {} - for f, i, flag in map(lambda x: x.strip().split(" ", 2), vf): + for f, i, flag in [x.strip().split(" ", 2) for x in vf]: self.iverb_map.update(informal_to_formal_conjucation(i, f, flag)) - with codecs.open(word_file, encoding="utf8") as wf: - self.iword_map = dict(map(lambda x: x.strip().split(" ", 1), wf)) + with open(word_file, encoding="utf8") as wf: + self.iword_map = dict([x.strip().split(" ", 1) for x in wf]) self.words = set() if self.seperation_flag: - self.words.update(self.iword_map.keys()) - self.words.update(self.iword_map.values()) - self.words.update(self.iverb_map.keys()) - self.words.update(self.iverb_map.values()) + self.words.update(list(self.iword_map.keys())) + self.words.update(list(self.iword_map.values())) + self.words.update(list(self.iverb_map.keys())) + self.words.update(list(self.iverb_map.values())) self.words.update(self.lemmatizer.words) - self.words.update(self.lemmatizer.verbs.keys()) - self.words.update(self.lemmatizer.verbs.values()) + self.words.update(list(self.lemmatizer.verbs.keys())) + self.words.update(list(self.lemmatizer.verbs.values())) - def split_token_words(self, token): + def split_token_words(self, token: str) -> str: """هرجایی در متن فاصله نیاز بود قرار می‌دهد. متأسفانه در برخی از متن‌ها، به بهانهٔ صرفه‌جویی در زمان یا از سرِ تنبلی، @@ -103,10 +101,10 @@ def split_token_words(self, token): ایجاد می‌کند و آن را به شکل صحیح برمی‌گرداند. Args: - token (str): توکنی که باید فاصله‌گذاری شود. + token: توکنی که باید فاصله‌گذاری شود. Returns: - (str): توکنی با فاصله‌گذاری صحیح. + توکنی با فاصله‌گذاری صحیح. """ @@ -135,13 +133,13 @@ def perm(lst): token = re.sub(r"(.)\1{2,}", r"\1", token) ps = perm(shekan(token)) for c in ps: - if set(map(lambda x: self.ilemmatizer.lemmatize(x), c)).issubset( + if {self.ilemmatizer.lemmatize(x) for x in c}.issubset( self.words ): return " ".join(c) return token - def normalized_word(self, word): + def normalized_word(self, word: str) -> list[str]: """اشکال مختلف نرمالایزشدهٔ کلمه را برمی‌گرداند. Examples: @@ -150,10 +148,10 @@ def normalized_word(self, word): ['می‌روم', 'می‌رم'] Args: - word(str): کلمه‌ای که باید نرمال‌سازی شود. + word: کلمه‌ای که باید نرمال‌سازی شود. Returns: - (List[str]): اشکال نرمالایزشدهٔ کلمه. + اشکال نرمالایزشدهٔ کلمه. """ @@ -729,7 +727,7 @@ def straightForwardResult(word): return possibleWords - def normalize(self, text): + def normalize(self, text: str) -> list[list[list[str]]]: """متن محاوره‌ای را به متن فارسی معیار تبدیل می‌کند. Examples: @@ -741,14 +739,14 @@ def normalize(self, text): [[['اجازه'], ['بدهیم'], ['همسرمان'], ['در'], ['جمع'], ['خانواده\u200cاش'], ['احساس'], ['آزادی'], ['کند'], ['و'], ['فکر'], ['نکند', 'نکنه'], ['که'], ['ما'], ['دائم'], ['حواسمان'], ['بهش'], ['هست'], ['.']]] Args: - text (str): متن محاوره‌ای که باید تبدیل به متن فارسی معیار شود. + text: متن محاوره‌ای که باید تبدیل به متن فارسی معیار شود. Returns: - (List[List[List[str]]]): متن فارسی معیار. + متن فارسی معیار. """ - text = super(InformalNormalizer, self).normalize(text) + text = super().normalize(text) sents = [ self.word_tokenizer.tokenize(sentence) for sentence in self.sent_tokenizer.tokenize(text) @@ -756,14 +754,14 @@ def normalize(self, text): return [[self.normalized_word(word) for word in sent] for sent in sents] - def informal_conjugations(self, verb): + def informal_conjugations(self, verb: str) -> list[str]: """صورت‌های صرفی فعل را در شکل محاوره‌ای تولید می‌کند. Args: - verb (str): فعلی که باید صرف شود. + verb: فعلی که باید صرف شود. Returns: - (List[str]): صورت‌های صرفی فعل. + صورت‌های صرفی فعل. """ ends = ["م", "ی", "", "یم", "ین", "ن"] @@ -791,7 +789,7 @@ def informal_conjugations(self, verb): class InformalLemmatizer(Lemmatizer): def __init__(self, **kargs): - super(InformalLemmatizer, self).__init__(**kargs) + super().__init__(**kargs) temp = [] self.words = set(self.words.keys()) @@ -808,12 +806,12 @@ def __init__(self, **kargs): self.verbs.update(temp) - with codecs.open(informal_verbs, encoding="utf8") as vf: - for f, i, flag in map(lambda x: x.strip().split(" ", 2), vf): - self.verbs.update(dict(map(lambda x: (x, f), self.iconjugations(i)))) + with open(informal_verbs, encoding="utf8") as vf: + for f, i, flag in [x.strip().split(" ", 2) for x in vf]: + self.verbs.update({x: f for x in self.iconjugations(i)}) - with codecs.open(informal_words, encoding="utf8") as wf: - self.words.update(map(lambda x: x.strip().split(" ", 1)[0], wf)) + with open(informal_words, encoding="utf8") as wf: + self.words.update([x.strip().split(" ", 1)[0] for x in wf]) def iconjugations(self, verb): ends = ["م", "ی", "", "یم", "ین", "ن"] diff --git a/hazm/Lemmatizer.py b/hazm/Lemmatizer.py index 8d8ce891..ab7b1a9b 100644 --- a/hazm/Lemmatizer.py +++ b/hazm/Lemmatizer.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """این ماژول شامل کلاس‌ها و توابعی برای ریشه‌یابی کلمات است. فرق بین [Lemmatizer](./Lemmatizer.md) و [Stemmer](./Stemmer.md) این است که @@ -12,31 +10,32 @@ """ -from __future__ import unicode_literals + from .utils import default_words, default_verbs from .Stemmer import Stemmer from .WordTokenizer import WordTokenizer -class Lemmatizer(object): +class Lemmatizer: """این کلاس شامل توابعی برای ریشه‌یابی کلمات است. Args: - words_file (str, optional): ریشه‌یابی کلمات از روی این فایل صورت + words_file: ریشه‌یابی کلمات از روی این فایل صورت می‌گیرد. هضم به صورت پیش‌فرض فایلی برای این منظور در نظر گرفته است؛ با این حال شما می‌توانید فایل موردنظر خود را معرفی کنید. برای آگاهی از ساختار این فایل به فایل پیش‌فرض مراجعه کنید. - verbs_file (str, optional): اشکال صرفی فعل از روی این فایل ساخته + verbs_file: اشکال صرفی فعل از روی این فایل ساخته می‌شود. هضم به صورت پیش‌فرض فایلی برای این منظور در نظر گرفته است؛ با این حال شما می‌توانید فایل موردنظر خود را معرفی کنید. برای آگاهی از ساختار این فایل به فایل پیش‌فرض مراجعه کنید. - joined_verb_parts (bool, optional): اگر `True` باشد افعال چندبخشی را با کاراکتر زیرخط به هم می‌چسباند. + joined_verb_parts: اگر `True` باشد افعال چندبخشی را با کاراکتر زیرخط به هم می‌چسباند. """ def __init__( - self, words_file=default_words, verbs_file=default_verbs, joined_verb_parts=True + self, words_file:str=default_words, verbs_file:str=default_verbs, joined_verb_parts:bool=True ): + self.words_file = words_file self.verbs = {} self.stemmer = Stemmer() @@ -57,7 +56,7 @@ def __init__( for before_verb in tokenizer.before_verbs: self.verbs[before_verb + "_" + bon] = verb - def lemmatize(self, word, pos=""): + def lemmatize(self, word:str, pos:str="") -> str: """ریشهٔ کلمه را پیدا می‌کند. پارامتر `pos` نوع کلمه است: (اسم، فعل، صفت و ...) و به این خاطر لازم @@ -83,11 +82,11 @@ def lemmatize(self, word, pos=""): 'اجتماعی' Args: - word (str): کلمه‌ای که باید پردازش شود. - pos (str, optional): نوع کلمه. این پارامتر سه مقدار `V` (فعل) و `AJ` (صفت) و `PRO` (ضمیر) را می‌پذیرد. + word: کلمه‌ای که باید پردازش شود. + pos: نوع کلمه. این پارامتر سه مقدار `V` (فعل) و `AJ` (صفت) و `PRO` (ضمیر) را می‌پذیرد. Returns: - (str): ریشهٔ کلمه + ریشهٔ کلمه """ if not pos and word in self.words: @@ -111,7 +110,7 @@ def lemmatize(self, word, pos=""): return word - def conjugations(self, verb): + def conjugations(self, verb:str) -> list[str]: """صورت‌های صرفی فعل را برمی‌گرداند. Examples: @@ -122,10 +121,10 @@ def conjugations(self, verb): ['آوردم', 'آوردی', 'آورد', 'آوردیم', 'آوردید', 'آوردند', 'نیاوردم', 'نیاوردی', 'نیاورد', 'نیاوردیم', 'نیاوردید', 'نیاوردند', 'آورم', 'آوری', 'آورد', 'آوریم', 'آورید', 'آورند', 'نیاورم', 'نیاوری', 'نیاورد', 'نیاوریم', 'نیاورید', 'نیاورند', 'می‌آوردم', 'می‌آوردی', 'می‌آورد', 'می‌آوردیم', 'می‌آوردید', 'می‌آوردند', 'نمی‌آوردم', 'نمی‌آوردی', 'نمی‌آورد', 'نمی‌آوردیم', 'نمی‌آوردید', 'نمی‌آوردند', 'آورده‌ام', 'آورده‌ای', 'آورده', 'آورده‌ایم', 'آورده‌اید', 'آورده‌اند', 'نیاورده‌ام', 'نیاورده‌ای', 'نیاورده', 'نیاورده‌ایم', 'نیاورده‌اید', 'نیاورده‌اند', 'آورم', 'آوری', 'آورد', 'آوریم', 'آورید', 'آورند', 'نیاورم', 'نیاوری', 'نیاورد', 'نیاوریم', 'نیاورید', 'نیاورند', 'می‌آورم', 'می‌آوری', 'می‌آورد', 'می‌آوریم', 'می‌آورید', 'می‌آورند', 'نمی‌آورم', 'نمی‌آوری', 'نمی‌آورد', 'نمی‌آوریم', 'نمی‌آورید', 'نمی‌آورند', 'بیاورم', 'بیاوری', 'بیاورد', 'بیاوریم', 'بیاورید', 'بیاورند', 'نیاورم', 'نیاوری', 'نیاورد', 'نیاوریم', 'نیاورید', 'نیاورند', 'بیاور', 'نیاور'] Args: - verb (str): فعلی که باید صرف شود. + verb: فعلی که باید صرف شود. Returns: - (List[str]): لیستِ صورت‌های صرفی فعل. + لیستِ صورت‌های صرفی فعل. """ @@ -143,7 +142,7 @@ def conjugations(self, verb): imperatives = ["ب" + present, "ن" + present] if present.endswith("ا") or present in ("آ", "گو"): - present = present + "ی" + present += "ی" ends = ["م", "ی", "د", "یم", "ید", "ند"] present_simples = [present + end for end in ends] @@ -153,10 +152,10 @@ def conjugations(self, verb): ] present_not_subjunctives = ["ن" + item for item in present_simples] - with_nots = lambda items: items + list(map(lambda item: "ن" + item, items)) + with_nots = lambda items: items + list(["ن" + item for item in items]) aa_refinement = ( lambda items: list( - map(lambda item: item.replace("بآ", "بیا").replace("نآ", "نیا"), items) + [item.replace("بآ", "بیا").replace("نآ", "نیا") for item in items] ) if items[0].startswith("آ") else items diff --git a/hazm/MirasTextReader.py b/hazm/MirasTextReader.py index 9d58f7b8..2f91a892 100644 --- a/hazm/MirasTextReader.py +++ b/hazm/MirasTextReader.py @@ -1,41 +1,37 @@ -# coding: utf-8 - """این ماژول شامل کلاس‌ها و توابعی برای خواندن پیکرهٔ میراث است. [پیکرهٔ میراث](https://github.com/miras-tech/MirasText) حاوی ۲,۸۳۵,۴۱۴ خبر از ۲۵۰ خبرگزاری فارسی است. """ - -from __future__ import unicode_literals -import codecs +from typing import Iterator class MirasTextReader: """این کلاس شامل توابعی برای خواندن پیکرهٔ میراث است. Args: - filename (str): مسیر فایلِ پیکره. + filename: مسیر فایلِ پیکره. """ - def __init__(self, filename): + def __init__(self, filename: str): self._filename = filename - def docs(self): + def docs(self) -> Iterator[dict[str,str]]: """خبرها را برمی‌گرداند. Yields: - (Dict): خبر بعدی. + خبر بعدی. """ - for line in codecs.open(self._filename, encoding="utf-8"): + for line in open(self._filename, encoding="utf-8"): parts = line.split("***") # todo: extract link, tags, ... yield {"text": parts[0].strip()} - def texts(self): + def texts(self) -> str: """فقط متن خبرها را برمی‌گرداند. این تابع صرفاً برای راحتی بیشتر تهیه شده وگرنه با تابع @@ -48,7 +44,7 @@ def texts(self): 'ایرانی‌ها چقدر از اینترنت استفاده می‌کنند؟' Yields: - (str): متنِ خبر بعدی. + : متنِ خبر بعدی. """ for doc in self.docs(): diff --git a/hazm/Normalizer.py b/hazm/Normalizer.py index 6313f688..5bf6ee05 100644 --- a/hazm/Normalizer.py +++ b/hazm/Normalizer.py @@ -1,41 +1,39 @@ -# coding: utf-8 - """این ماژول شامل کلاس‌ها و توابعی برای نرمال‌سازی متن است. """ -from __future__ import unicode_literals + import re from .Lemmatizer import Lemmatizer from .WordTokenizer import WordTokenizer from .utils import maketrans, regex_replace -class Normalizer(object): +class Normalizer: """این کلاس شامل توابعی برای نرمال‌سازی متن است. Args: - correct_spacing (bool, optional): اگر `True‍` فاصله‌گذاری‌ها را در متن، نشانه‌های سجاوندی و پیشوندها و پسوندها اصلاح می‌کند. - remove_diacritics (bool, optional): اگر `True` باشد اعرابِ حروف را حذف می‌کند. - remove_specials_chars (bool, optional): اگر `True` باشد برخی از کاراکترها و نشانه‌های خاص را که کاربردی در پردازش متن ندارند حذف می‌کند. - decrease_repeated_chars (bool, optional): اگر `True` باشد تکرارهای بیش از ۲ بار را به ۲ بار کاهش می‌دهد. مثلاً «سلاممم» را به «سلامم» تبدیل می‌کند. - persian_style (bool, optional): اگر `True` باشد اصلاحات مخصوص زبان فارسی را انجام می‌دهد؛ مثلاً جایگزین‌کردن کوتیشن با گیومه. - persian_numbers (bool, optional): اگر `True` باشد ارقام انگلیسی را با فارسی جایگزین می‌کند. - unicodes_replacement (bool, optional): اگر `True` باشد برخی از کاراکترهای یونیکد را با معادل نرمال‌شدهٔ آن جایگزین می‌کند. - seperate_mi (bool, optional): اگر `True` باشد پیشوند «می» و «نمی» را در افعال جدا می‌کند. + correct_spacing: اگر `True‍` فاصله‌گذاری‌ها را در متن، نشانه‌های سجاوندی و پیشوندها و پسوندها اصلاح می‌کند. + remove_diacritics: اگر `True` باشد اعرابِ حروف را حذف می‌کند. + remove_specials_chars: اگر `True` باشد برخی از کاراکترها و نشانه‌های خاص را که کاربردی در پردازش متن ندارند حذف می‌کند. + decrease_repeated_chars: اگر `True` باشد تکرارهای بیش از ۲ بار را به ۲ بار کاهش می‌دهد. مثلاً «سلاممم» را به «سلامم» تبدیل می‌کند. + persian_style اگر `True` باشد اصلاحات مخصوص زبان فارسی را انجام می‌دهد؛ مثلاً جایگزین‌کردن کوتیشن با گیومه. + persian_numbers: اگر `True` باشد ارقام انگلیسی را با فارسی جایگزین می‌کند. + unicodes_replacement: اگر `True` باشد برخی از کاراکترهای یونیکد را با معادل نرمال‌شدهٔ آن جایگزین می‌کند. + seperate_mi: اگر `True` باشد پیشوند «می» و «نمی» را در افعال جدا می‌کند. """ def __init__( self, - correct_spacing=True, - remove_diacritics=True, - remove_specials_chars=True, - decrease_repeated_chars=True, - persian_style=True, - persian_numbers=True, - unicodes_replacement=True, - seperate_mi=True, + correct_spacing:bool=True, + remove_diacritics:bool=True, + remove_specials_chars:bool=True, + decrease_repeated_chars:bool=True, + persian_style:bool=True, + persian_numbers:bool=True, + unicodes_replacement:bool=True, + seperate_mi:bool=True, ): self._correct_spacing = correct_spacing self._remove_diacritics = remove_diacritics @@ -93,7 +91,7 @@ def __init__( ("([" + punc_before + "]) ", r"\1"), # remove space after # put space after . and : ( - "([" + punc_after[:3] + "])([^ " + punc_after + "\d۰۱۲۳۴۵۶۷۸۹])", + "([" + punc_after[:3] + "])([^ " + punc_after + r"\d۰۱۲۳۴۵۶۷۸۹])", r"\1 \2", ), ( @@ -105,9 +103,9 @@ def __init__( r"\1 \2", ), # put space before # put space after number; e.g., به طول ۹متر -> به طول ۹ متر - ("(\d)([آابپتثجچحخدذرزژسشصضطظعغفقکگلمنوهی])", r"\1 \2"), + (r"(\d)([آابپتثجچحخدذرزژسشصضطظعغفقکگلمنوهی])", r"\1 \2"), # put space after number; e.g., به طول۹ -> به طول ۹ - ("([آابپتثجچحخدذرزژسشصضطظعغفقکگلمنوهی])(\d)", r"\1 \2"), + (r"([آابپتثجچحخدذرزژسشصضطظعغفقکگلمنوهی])(\d)", r"\1 \2"), ] self.affix_spacing_patterns = [ @@ -136,7 +134,7 @@ def __init__( if self._persian_style: self.persian_style_patterns = [ ('"([^\n"]+)"', r"«\1»"), # replace quotation with gyoome - ("([\d+])\.([\d+])", r"\1٫\2"), # replace dot with momayez + (r"([\d+])\.([\d+])", r"\1٫\2"), # replace dot with momayez (r" ?\.\.\.", " …"), # replace 3 dots ] @@ -184,7 +182,7 @@ def __init__( ("ﻵ|ﻶ|ﻷ|ﻸ|ﻹ|ﻺ|ﻻ|ﻼ", "لا"), ] - def normalize(self, text): + def normalize(self, text: str) -> str: """متن را نرمال‌سازی می‌کند. Examples: @@ -193,10 +191,10 @@ def normalize(self, text): 'اعلام کرد: «زمین‌لرزه‌ای به بزرگی ۶ دهم ریشتر …»' Args: - text (str): متنی که باید نرمال‌سازی شود. + text: متنی که باید نرمال‌سازی شود. Returns: - (str): متنِ نرمال‌سازی‌شده. + متنِ نرمال‌سازی‌شده. """ @@ -247,7 +245,7 @@ def correct_spacing(self, text): return text - def remove_diacritics(self, text): + def remove_diacritics(self, text: str) -> str: """اِعراب را از متن حذف می‌کند. Examples: @@ -256,15 +254,15 @@ def remove_diacritics(self, text): 'حذف اعراب' Args: - text (str): متنی که باید اعراب آن حذف شود. + text: متنی که باید اعراب آن حذف شود. Returns: - (str): متنی بدون اعراب. + متنی بدون اعراب. """ return regex_replace(self.diacritics_patterns, text) - def remove_specials_chars(self, text): + def remove_specials_chars(self, text: str) -> str: """برخی از کاراکترها و نشانه‌های خاص را که کاربردی در پردازش متن ندارند حذف می‌کند. @@ -274,15 +272,15 @@ def remove_specials_chars(self, text): 'پیامبر اکرم ' Args: - text (str): متنی که باید کاراکترها و نشانه‌های اضافهٔ آن حذف شود. + text: متنی که باید کاراکترها و نشانه‌های اضافهٔ آن حذف شود. Returns: - (str): متنی بدون کاراکترها و نشانه‌های اضافه. + متنی بدون کاراکترها و نشانه‌های اضافه. """ return regex_replace(self.specials_chars_patterns, text) - def decrease_repeated_chars(self, text): + def decrease_repeated_chars(self, text: str) -> str: """تکرارهای زائد حروف را در کلماتی مثل سلامممممم حذف می‌کند و در مواردی که نمی‌تواند تشخیص دهد دست کم به دو تکرار کاهش می‌دهد. @@ -292,10 +290,10 @@ def decrease_repeated_chars(self, text): 'سلام به همه' Args: - text (str): متنی که باید تکرارهای زائد آن حذف شود. + text: متنی که باید تکرارهای زائد آن حذف شود. Returns: - (str): متنی بدون کاراکترهای زائد یا حداقل با دو تکرار. + متنی بدون کاراکترهای زائد یا حداقل با دو تکرار. """ @@ -315,7 +313,7 @@ def decrease_repeated_chars(self, text): return text - def persian_style(self, text): + def persian_style(self, text: str) -> str: """برخی از حروف و نشانه‌ها را با حروف و نشانه‌های فارسی جایگزین می‌کند. Examples: @@ -324,15 +322,15 @@ def persian_style(self, text): '«نرمال‌سازی»' Args: - text (str): متنی که باید حروف و نشانه‌های آن با حروف و نشانه‌های فارسی جایگزین شود. + text: متنی که باید حروف و نشانه‌های آن با حروف و نشانه‌های فارسی جایگزین شود. Returns: - (str): متنی با حروف و نشانه‌های فارسی‌سازی شده. + متنی با حروف و نشانه‌های فارسی‌سازی شده. """ return regex_replace(self.persian_style_patterns, text) - def persian_number(self, text): + def persian_number(self, text: str) -> str: """اعداد لاتین و علامت % را با معادل فارسی آن جایگزین می‌کند Examples: @@ -341,10 +339,10 @@ def persian_number(self, text): '۵٪ رشد داشته است.' Args: - text (str): متنی که باید اعداد لاتین و علامت % آن با معادل فارسی جایگزین شود. + text: متنی که باید اعداد لاتین و علامت % آن با معادل فارسی جایگزین شود. Returns: - (str): متنی با اعداد و علامت ٪ فارسی. + متنی با اعداد و علامت ٪ فارسی. """ translations = maketrans( @@ -352,7 +350,7 @@ def persian_number(self, text): ) return text.translate(translations) - def unicodes_replacement(self, text): + def unicodes_replacement(self, text: str) -> str: """برخی از کاراکترهای خاص یونیکد را با معادلِ نرمال آن جایگزین می‌کند. غالباً این کار فقط در مواردی صورت می‌گیرد که یک کلمه در قالب یک کاراکتر یونیکد تعریف شده است. @@ -379,10 +377,10 @@ def unicodes_replacement(self, text): 'پیامبر اکرم ' Args: - text (str): متنی که باید برخی از کاراکترهای یونیکد آن (جدول بالا)، با شکل استاندارد، جایگزین شود. + text: متنی که باید برخی از کاراکترهای یونیکد آن (جدول بالا)، با شکل استاندارد، جایگزین شود. Returns: - (str): متنی که برخی از کاراکترهای یونیکد آن با شکل استاندارد جایگزین شده است. + متنی که برخی از کاراکترهای یونیکد آن با شکل استاندارد جایگزین شده است. """ @@ -391,7 +389,7 @@ def unicodes_replacement(self, text): return text - def seperate_mi(self, text): + def seperate_mi(self, text: str) -> str: """پیشوند «می» و «نمی» را در افعال جدا کرده و با نیم‌فاصله می‌چسباند. Examples: @@ -400,10 +398,10 @@ def seperate_mi(self, text): 'نمی‌دانم چه می‌گفت' Args: - text (str): متنی که باید پیشوند «می» و «نمی» در آن جدا شود. + text: متنی که باید پیشوند «می» و «نمی» در آن جدا شود. Returns: - (str): متنی با «می» و «نمی» جدا شده. + متنی با «می» و «نمی» جدا شده. """ matches = re.findall(r"\bن?می[آابپتثجچحخدذرزژسشصضطظعغفقکگلمنوهی]+", text) @@ -414,7 +412,7 @@ def seperate_mi(self, text): return text - def token_spacing(self, tokens): + def token_spacing(self, tokens: list[str]) -> list[str]: """توکن‌های ورودی را به فهرستی از توکن‌های نرمال‌سازی شده تبدیل می‌کند. در این فرایند ممکن است برخی از توکن‌ها به یکدیگر بچسبند؛ برای مثال: `['زمین', 'لرزه', 'ای']` تبدیل می‌شود به: `['زمین‌لرزه‌ای']` @@ -433,10 +431,10 @@ def token_spacing(self, tokens): ['زمین‌لرزه‌ای'] Args: - tokens (List[str]): توکن‌هایی که باید نرمال‌سازی شود. + tokens: توکن‌هایی که باید نرمال‌سازی شود. Returns: - (List[str]): لیستی از توکن‌های نرمال‌سازی شده به شکل `[token1, token2, ...]`. + لیستی از توکن‌های نرمال‌سازی شده به شکل `[token1, token2, ...]`. """ # >>> normalizer.token_spacing(['پرداخت', 'شده', 'است']) diff --git a/hazm/POSTagger.py b/hazm/POSTagger.py index 749c354c..879f95f0 100755 --- a/hazm/POSTagger.py +++ b/hazm/POSTagger.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """این ماژول شامل کلاس‌ها و توابعی برای برچسب‌گذاری توکن‌هاست. **میزان دقت برچسب‌زنی در نسخهٔ حاضر ۹۷.۱ درصد [^1] است.** [^1]: @@ -7,7 +5,7 @@ """ -from __future__ import unicode_literals + from nltk.tag import stanford from .SequenceTagger import SequenceTagger @@ -27,7 +25,7 @@ class StanfordPOSTagger(stanford.StanfordPOSTagger): """ - def __init__(self, model_filename, path_to_jar, *args, **kwargs): + def __init__(self, model_filename, path_to_jar, *args, **kwargs): self._SEPARATOR = "/" super(stanford.StanfordPOSTagger, self).__init__( model_filename=model_filename, path_to_jar=path_to_jar, *args, **kwargs @@ -48,5 +46,5 @@ def tag_sents(self, sentences): """ """ - refined = map(lambda s: [w.replace(" ", "_") for w in s], sentences) + refined = [[w.replace(" ", "_") for w in s] for s in sentences] return super(stanford.StanfordPOSTagger, self).tag_sents(refined) diff --git a/hazm/PersicaReader.py b/hazm/PersicaReader.py index 17f895bf..887ed339 100644 --- a/hazm/PersicaReader.py +++ b/hazm/PersicaReader.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """این ماژول شامل کلاس‌ها و توابعی برای خواندن پیکرهٔ پرسیکا است. [پیکرهٔ پرسیکا](https://www.peykaregan.ir/dataset/%D9%BE%D8%B1%D8%B3%DB%8C%DA%A @@ -12,20 +10,18 @@ داده‌کاوی است. """ - -from __future__ import print_function -import codecs +from typing import Iterator class PersicaReader: """این کلاس شامل توابعی برای خواندن پیکرهٔ پرسیکا است. Args: - csv_file (str): مسیر فایلِ پیکره با پسوند csv. + csv_file: مسیر فایلِ پیکره با پسوند csv. """ - def __init__(self, csv_file): + def __init__(self, csv_file: str) -> Iterator[dict[str, str]]: self._csv_file = csv_file def docs(self): @@ -47,11 +43,11 @@ def docs(self): 843656 Yields: - (Dict): خبر بعدی. + خبر بعدی. """ lines = [] - for line in codecs.open(self._csv_file, encoding="utf-8-sig"): + for line in open(self._csv_file, encoding="utf-8-sig"): line = line.strip() if line: if line.endswith(","): @@ -69,7 +65,7 @@ def docs(self): } lines = [] - def texts(self): + def texts(self) -> Iterator[str]: """فقط متن خبرها را برمی‌گرداند. این تابع صرفاً برای راحتی بیشتر تهیه شده وگرنه با همان تابع @@ -82,7 +78,7 @@ def texts(self): True Yields: - (str): متنِ خبر بعدی. + متنِ خبر بعدی. """ for doc in self.docs(): diff --git a/hazm/PeykareReader.py b/hazm/PeykareReader.py index 8ec43a22..908034d8 100644 --- a/hazm/PeykareReader.py +++ b/hazm/PeykareReader.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """این ماژول شامل کلاس‌ها و توابعی برای خواندن پیکرهٔ Peykare است. [peykare پیکرهٔ](https://www.peykaregan.ir/dataset/%D9%BE%DB%8C%DA%A9%D8%B1%D9% @@ -17,13 +15,15 @@ """ -from __future__ import unicode_literals + import os, codecs +from typing import Iterator + from .Normalizer import Normalizer from .WordTokenizer import WordTokenizer -def coarse_pos_u(tags, word): +def coarse_pos_u(tags:list[str], word:str) -> list[str]: """برچسب‌های ریز را به برچسب‌های درشت منطبق با استاندارد جهانی (coarse-grained universal pos tags) تبدیل می‌کند. @@ -32,10 +32,10 @@ def coarse_pos_u(tags, word): 'NOUN' Args: - tags (List[str]): لیست برچسب‌های ریز. + tags: لیست برچسب‌های ریز. Returns: - (List[str]): لیست برچسب‌های درشت جهانی. + لیست برچسب‌های درشت جهانی. """ @@ -162,7 +162,7 @@ def coarse_pos_u(tags, word): return "NOUN" -def coarse_pos_e(tags, word): +def coarse_pos_e(tags: list[str], word) -> list[str]: """برچسب‌های ریز را به برچسب‌های درشت (coarse-grained pos tags) تبدیل می‌کند. Examples: @@ -170,10 +170,10 @@ def coarse_pos_e(tags, word): 'N' Args: - tags (List[str]): لیست برچسب‌های ریز. + tags: لیست برچسب‌های ریز. Returns: - (List[str]): لیست برچسب‌های درشت. + لیست برچسب‌های درشت. """ @@ -201,7 +201,7 @@ def coarse_pos_e(tags, word): return "N" -def join_verb_parts(sentence): +def join_verb_parts(sentence: list[tuple[str,str]]) -> list[tuple[str, str]]: """جمله را در قالب لیستی از `(توکن، برچسب)‌`ها می‌گیرد و توکن‌های مربوط به افعال چندبخشی را با کاراکتر زیرخط (_) به هم می‌چسباند. @@ -210,10 +210,10 @@ def join_verb_parts(sentence): [('اولین', 'AJ'), ('سیاره', 'Ne'), ('خارج', 'AJ'), ('از', 'P'), ('منظومه', 'Ne'), ('شمسی', 'AJ'), ('دیده_شد', 'V'), ('.', 'PUNC')] Args: - sentence(List[Tuple[str,str]]): جمله در قالب لیستی از `(توکن، برچسب)`ها. + sentence: جمله در قالب لیستی از `(توکن، برچسب)`ها. Returns: - (List[Tuple[str, str]): لیستی از `(توکن، برچسب)`ها که در آن افعال چندبخشی در قالب یک توکن با کاراکتر زیرخط به هم چسبانده شده‌اند. + لیستی از `(توکن، برچسب)`ها که در آن افعال چندبخشی در قالب یک توکن با کاراکتر زیرخط به هم چسبانده شده‌اند. """ @@ -240,14 +240,14 @@ class PeykareReader: """این کلاس شامل توابعی برای خواندن پیکرهٔ Peykare است. Args: - root (str): آدرس فولدر حاوی فایل‌های پیکره. - join_verb_parts (bool, optional): اگر `True‍` باشد افعال چندقسمتی به‌شکل چسبیده‌به‌هم برگردانده_می‌شود. - pos_map (str): دیکشنری مبدل برچسب‌های ریز به درشت. + root: آدرس فولدر حاوی فایل‌های پیکره. + join_verb_parts: اگر `True‍` باشد افعال چندقسمتی به‌شکل چسبیده‌به‌هم برگردانده_می‌شود. + pos_map: دیکشنری مبدل برچسب‌های ریز به درشت. """ def __init__( - self, root, joined_verb_parts=True, pos_map=coarse_pos_e, universal_pos=False + self, root: str, joined_verb_parts: bool=True, pos_map: str=coarse_pos_e, universal_pos:bool=False ): self._root = root if pos_map is None: @@ -259,11 +259,11 @@ def __init__( self._joined_verb_parts = joined_verb_parts self._normalizer = Normalizer(correct_spacing=False) - def docs(self): + def docs(self) -> Iterator[str]: """اسناد را به شکل متن خام برمی‌گرداند. Yields: - (str): متن خام سند بعدی. + متن خام سند بعدی. """ @@ -276,16 +276,16 @@ def docs(self): if text: yield text - def doc_to_sents(self, document): + def doc_to_sents(self, document: str) -> list[[str,str]]: """سند ورودی را به لیستی از جملات تبدیل می‌کند. هر جمله لیستی از `(کلمه, برچسب)`ها است. Args: - document (str): سندی که باید تبدیل شود. + document: سندی که باید تبدیل شود. Yields: - (List[(str,str)]): `ها جملهٔ بعدی در قالب لیستی از `(کلمه، برچسب). + `ها جملهٔ بعدی در قالب لیستی از `(کلمه، برچسب). """ @@ -305,7 +305,7 @@ def doc_to_sents(self, document): yield sentence sentence = [] - def sents(self): + def sents(self) -> list[tuple[str,str]]: """جملات پیکره را در قالب لیستی از `(توکن، برچسب)`ها برمی‌گرداند. Examples: @@ -314,7 +314,7 @@ def sents(self): [('دیرزمانی', 'N'), ('از', 'P'), ('راه‌اندازی', 'Ne'), ('شبکه‌ی', 'Ne'), ('خبر', 'Ne'), ('الجزیره', 'N'), ('نمی‌گذرد', 'V'), ('،', 'PUNC'), ('اما', 'CONJ'), ('این', 'DET'), ('شبکه‌ی', 'Ne'), ('خبری', 'AJe'), ('عربی', 'N'), ('بسیار', 'ADV'), ('سریع', 'ADV'), ('توانسته', 'V'), ('در', 'P'), ('میان', 'Ne'), ('شبکه‌های', 'Ne'), ('عظیم', 'AJe'), ('خبری', 'AJ'), ('و', 'CONJ'), ('بنگاه‌های', 'Ne'), ('چندرسانه‌ای', 'AJe'), ('دنیا', 'N'), ('خودی', 'N'), ('نشان', 'N'), ('دهد', 'V'), ('.', 'PUNC')] Yields: - (List[Tuple[str,str]]): جملهٔ بعدی در قالب لیستی از `(توکن، برچسب)`ها. + جملهٔ بعدی در قالب لیستی از `(توکن، برچسب)`ها. """ # >>> peykare = PeykareReader(root='corpora/peykare', joined_verb_parts=False, pos_map=None) diff --git a/hazm/QuranCorpusReader.py b/hazm/QuranCorpusReader.py index 3d4d33bc..0b2d5de1 100644 --- a/hazm/QuranCorpusReader.py +++ b/hazm/QuranCorpusReader.py @@ -1,14 +1,11 @@ -# coding: utf-8 - """این ماژول شامل کلاس‌ها و توابعی برای خواندن پیکرهٔ Quranic Arabic است. پیکرهٔ [Quranic Arabic](https://corpus.quran.com/) شامل قواعد نحوی و اطلاعات ریخت‌شناسی تک‌تک کلمات قرآن کریم است. """ +from typing import Iterator -from __future__ import unicode_literals -import codecs from .utils import maketrans buckwalter_transliteration = maketrans( @@ -21,14 +18,14 @@ class QuranCorpusReader: """این کلاس شامل توابعی برای خواندن پیکرهٔ Quranic Arabic است. Args: - quran_file (str): مسیر فایلِ پیکره + quran_file: مسیر فایلِ پیکره """ - def __init__(self, quran_file): + def __init__(self, quran_file: str): self._quran_file = quran_file - def parts(self): + def parts(self) -> Iterator[dict[str, str]]: """اجزای متن قرآن را به‌همراه اطلاعات نحوی‌شان برمی‌گرداند. یک جزء لزوماً یک کلمه نیست؛ مثلاً واژهٔ «الرحمن» از دو جزء «ال» و «رحمن» تشکیل @@ -44,10 +41,10 @@ def parts(self): {'loc': (1, 1, 2, 1), 'text': 'ٱللَّهِ', 'tag': 'PN', 'lem': 'ٱللَّه', 'root': 'اله'} Yields: - (Dict): جزء بعدی متن قرآن. + جزء بعدی متن قرآن. """ - for line in codecs.open(self._quran_file): + for line in open(self._quran_file): if not line.startswith("("): continue parts = line.strip().split("\t") @@ -66,7 +63,7 @@ def parts(self): part["root"] = feature[5:].translate(buckwalter_transliteration) yield part - def words(self): + def words(self) -> Iterator[tuple[str,str,str,str,str,list[dict]]]: """اطلاعات صرفی کلمات قرآن را برمی‌گرداند. Examples: @@ -75,7 +72,7 @@ def words(self): ('1.1.1', 'بِسْمِ', 'ٱسْم', 'سمو', 'P-N', [{'text': 'بِ', 'tag': 'P'}, {'text': 'سْمِ', 'tag': 'N', 'lem': 'ٱسْم', 'root': 'سمو'}]) Yields: - (Tuple[str,str,str,str,str,List[Dict]]): اطلاعات صرفی کلمهٔ بعدی قرآن. + اطلاعات صرفی کلمهٔ بعدی قرآن. """ diff --git a/hazm/SentenceTokenizer.py b/hazm/SentenceTokenizer.py index 157e6ee0..efbe0cc7 100644 --- a/hazm/SentenceTokenizer.py +++ b/hazm/SentenceTokenizer.py @@ -1,12 +1,10 @@ -# coding: utf-8 - """این ماژول شامل کلاس‌ها و توابعی برای استخراج جملاتِ متن است. برای استخراج کلمات از تابع [WordTokenizer()][hazm.WordTokenizer] استفاده کنید. """ -from __future__ import unicode_literals + import re from nltk.tokenize.api import TokenizerI @@ -17,9 +15,9 @@ class SentenceTokenizer(TokenizerI): """ def __init__(self): - self.pattern = re.compile(r"([!\.\?⸮؟]+)[ \n]+") + self.pattern = re.compile(r'([!.?⸮؟]+)[ \n]+') - def tokenize(self, text): + def tokenize(self, text: str) -> list[str]: """متن ورودی را به جملات سازندهٔ آن می‌شِکند. Examples: @@ -28,10 +26,10 @@ def tokenize(self, text): ['جدا کردن ساده است.', 'تقریبا البته!'] Args: - text (str): متنی که باید جملات آن استخراج شود. + text: متنی که باید جملات آن استخراج شود. Returns: - (List[str]): فهرست جملات استخراج‌شده. + فهرست جملات استخراج‌شده. """ text = self.pattern.sub(r"\1\n\n", text) diff --git a/hazm/SentiPersReader.py b/hazm/SentiPersReader.py index 487c35ff..a9063a3d 100644 --- a/hazm/SentiPersReader.py +++ b/hazm/SentiPersReader.py @@ -1,13 +1,12 @@ -# coding: utf-8 - """این ماژول شامل کلاس‌ها و توابعی برای خواندن پیکرهٔ سِنتی‌پِرِس است. سِنتی‌پرس شامل مجموعه‌ای از متون فارسی با برچسب‌های معنایی است. """ -from __future__ import unicode_literals, print_function + import os, sys, itertools +from typing import Iterator from xml.dom import minidom @@ -15,14 +14,14 @@ class SentiPersReader: """این کلاس شامل توابعی برای خواندن پیکرهٔ سِنتی‌پِرِس است. Args: - root (str): مسیر فولدر حاوی فایل‌های پیکره + root: مسیر فولدر حاوی فایل‌های پیکره """ - def __init__(self, root): + def __init__(self, root: str): self._root = root - def docs(self): + def docs(self) -> Iterator[dict]: """متن‌های فارسی را در قالب یک برمی‌گرداند. هر متن شامل این فیلدهاست: @@ -40,7 +39,7 @@ def docs(self): - جملات (sentences) Yields: - (Dict): متن بعدی. + متن بعدی. """ @@ -103,7 +102,7 @@ def element_sentences(element): except Exception as e: print("error in reading", filename, e, file=sys.stderr) - def comments(self): + def comments(self) -> Iterator[str]: """نظرات مربوط به متن را برمی‌گرداند. Examples: @@ -112,7 +111,7 @@ def comments(self): 'بيشتر مناسب است براي کساني که به دنبال تنوع هستند و در همه چيز نو گرايي دارند .' Yields: - (str): نظر بعدی. + نظر بعدی. """ for doc in self.docs(): diff --git a/hazm/SequenceTagger.py b/hazm/SequenceTagger.py index 167c9c49..ffeae868 100644 --- a/hazm/SequenceTagger.py +++ b/hazm/SequenceTagger.py @@ -1,10 +1,8 @@ -# coding: utf-8 - """این ماژول شامل کلاس‌ها و توابعی برای برچسب‌گذاری توکن‌هاست. """ -from __future__ import unicode_literals + from nltk.tag.api import TaggerI from nltk.metrics import accuracy @@ -14,17 +12,17 @@ class SequenceTagger(TaggerI): wrapper برای کتابخانهٔ [Wapiti](https://wapiti.limsi.fr/) است. Args: - patterns (List, optional): الگوهای لازم برای ساخت مدل. - **options (Dict, optional): آرگومان‌های نامدارِ اختیاری. + patterns: الگوهای لازم برای ساخت مدل. + **option: آرگومان‌های نامدارِ اختیاری. """ - def __init__(self, patterns=[], **options): + def __init__(self, patterns:list=[], **options:dict): from wapiti import Model self.model = Model(patterns="\n".join(patterns), **options) - def train(self, sentences): + def train(self, sentences:list[list[tuple[str,str]]]): """لیستی از جملات را می‌گیرد و بر اساس آن مدل را آموزش می‌دهد. هر جمله، لیستی از `(توکن، برچسب)`هاست. @@ -34,14 +32,14 @@ def train(self, sentences): >>> tagger.train([[('من', 'PRO'), ('به', 'P'), ('مدرسه', 'N'), ('رفته_بودم', 'V'), ('.', 'PUNC')]]) Args: - sentences (List[List[Tuple[str,str]]]): جملاتی که مدل از روی آن‌ها آموزش می‌بیند. + sentences: جملاتی که مدل از روی آن‌ها آموزش می‌بیند. """ self.model.train( ["\n".join([" ".join(word) for word in sentence]) for sentence in sentences] ) - def save_model(self, filename): + def save_model(self, filename:str): """مدل تهیه‌شده توسط تابع [train()][hazm.SequenceTagger.SequenceTagger.train] را ذخیره می‌کند. @@ -51,12 +49,12 @@ def save_model(self, filename): >>> tagger.save_model('resources/test.model') Args: - filename (str): نام و مسیر فایلی که می‌خواهید مدل در آن ذخیره شود. + filename: نام و مسیر فایلی که می‌خواهید مدل در آن ذخیره شود. """ self.model.save(filename) - def tag(self, tokens): + def tag(self, tokens: list[str]) -> list[tuple[str,str]]: """یک جمله را در قالب لیستی از توکن‌ها دریافت می‌کند و در خروجی لیستی از `(توکن، برچسب)`ها برمی‌گرداند. @@ -66,15 +64,15 @@ def tag(self, tokens): [('من', 'PRO'), ('به', 'P'), ('مدرسه', 'N'), ('رفته_بودم', 'V'), ('.', 'PUNC')] Args: - tokens (List[str]): لیستی از توکن‌های یک جمله که باید برچسب‌گذاری شود. + tokens: لیستی از توکن‌های یک جمله که باید برچسب‌گذاری شود. Returns: - (List[Tuple[str,str]]): ‌لیستی از `(توکن، برچسب)`ها. + ‌لیستی از `(توکن، برچسب)`ها. """ return self.tag_sents([tokens])[0] - def tag_sents(self, sentences): + def tag_sents(self, sentences: list[list[str]]) -> list[list[tuple[str,str]]]: """جملات را در قالب لیستی از توکن‌ها دریافت می‌کند و در خروجی، لیستی از لیستی از `(توکن، برچسب)`ها برمی‌گرداند. @@ -86,11 +84,11 @@ def tag_sents(self, sentences): [[('من', 'PRO'), ('به', 'P'), ('مدرسه', 'N'), ('رفته_بودم', 'V'), ('.', 'PUNC')]] Args: - sentences (List[List[str]]): لیستی از جملات که باید برچسب‌گذاری شود. + sentences لیستی از جملات که باید برچسب‌گذاری شود. Returns: - (List[List[Tuple[str,str]]]): لیستی از لیستی از `(توکن، برچسب)`ها. - هر لیست از `(توکن،برچسب)`ها مربوط به یک جمله است. + لیستی از لیستی از `(توکن، برچسب)`ها. + هر لیست از `(توکن،برچسب)`ها مربوط به یک جمله است. """ sentences = list(sentences) @@ -107,7 +105,7 @@ class IOBTagger(SequenceTagger): """ - def tag_sents(self, sentences): + def tag_sents(self, sentences: list[list[str]]) -> list[list[dict[tuple[str,str,str]]]]: """ Examples: @@ -137,6 +135,6 @@ def evaluate(self, gold): """ tagged_sents = self.tag_sents( - ([word[:-1] for word in sentence] for sentence in gold) + [word[:-1] for word in sentence] for sentence in gold ) return accuracy(sum(gold, []), sum(tagged_sents, [])) diff --git a/hazm/Stemmer.py b/hazm/Stemmer.py index e083643f..32c7e800 100644 --- a/hazm/Stemmer.py +++ b/hazm/Stemmer.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """این ماژول شامل کلاس‌ها و توابعی برای ریشه‌یابی کلمات است. فرق بین [Lemmatizer](./Lemmatizer.md) و [Stemmer](./Stemmer.md) این است که @@ -12,7 +10,7 @@ """ -from __future__ import unicode_literals + from nltk.stem.api import StemmerI @@ -38,7 +36,7 @@ def __init__(self): "‌", ] - def stem(self, word): + def stem(self, word: str)->str: """ریشهٔ کلمه را پیدا می‌کند. Examples: @@ -59,10 +57,10 @@ def stem(self, word): 'محبوب' Args: - word (str): کلمه‌ای که باید ریشهٔ آن پیدا شود. + word: کلمه‌ای که باید ریشهٔ آن پیدا شود. Returns: - (str): ریشهٔ کلمه. + ریشهٔ کلمه. """ diff --git a/hazm/TNewsReader.py b/hazm/TNewsReader.py index 0ba64a21..28f6d668 100644 --- a/hazm/TNewsReader.py +++ b/hazm/TNewsReader.py @@ -1,13 +1,12 @@ -# coding: utf-8 - """این ماژول شامل کلاس‌ها و توابعی برای خواندن پیکرهٔ تی‌نیوز است. """ -from __future__ import print_function + import os import sys import re +from typing import Iterator from xml.dom import minidom @@ -15,15 +14,15 @@ class TNewsReader: """این کلاس شامل توابعی برای خواندن پیکرهٔ تی‌نیوز است. Args: - root (str): مسیر فولدر حاوی فایل‌های پیکره. + root: مسیر فولدر حاوی فایل‌های پیکره. """ - def __init__(self, root): + def __init__(self, root: str): self._root = root self.cleaner = re.compile(r"<[^<>]+>") - def docs(self): + def docs(self) -> Iterator[dict]: """خبرها را در قالب یک `iterator` برمی‌گرداند. هر خبر، شی‌ای متشکل از چند پارامتر است: @@ -97,7 +96,7 @@ def get_text(element): except Exception as e: print("error in reading", name, e, file=sys.stderr) - def texts(self): + def texts(self) -> Iterator[str]: """فقط متن خبرها را برمی‌گرداند. این تابع صرفاً برای راحتی بیشتر تهیه شده وگرنه با همان تابع @@ -110,7 +109,7 @@ def texts(self): True Yields: - (str): متن خبر بعدی. + متن خبر بعدی. """ for doc in self.docs(): diff --git a/hazm/TokenSplitter.py b/hazm/TokenSplitter.py index 2ce9631f..a952fc4e 100644 --- a/hazm/TokenSplitter.py +++ b/hazm/TokenSplitter.py @@ -1,10 +1,8 @@ -# coding: utf-8 - """این ماژول شامل کلاس‌ها و توابعی برای تجزیه توکن به دو توکن کوچکتر است. """ -from __future__ import unicode_literals + from .Lemmatizer import Lemmatizer @@ -18,7 +16,7 @@ def __init__(self): self.lemmatize = self.lemmatizer.lemmatize self.words = self.lemmatizer.words - def split_token_words(self, token): + def split_token_words(self, token:str)-> list[tuple[str,str]]: """توکنِ ورودی را به دو توکن کوچکتر تجزیه می‌کند. اگر توکن به بیش از یک روش قابل تجزیه باشد همهٔ حالت‌های ممکن را @@ -38,10 +36,10 @@ def split_token_words(self, token): [('دستان', 'سرا')] Args: - token (str): توکنی که باید پردازش شود. + token: توکنی که باید پردازش شود. Returns: - ([List[Tuple[str,str]]]): لیستی از `[(توکن, توکن), (توکن, توکن), …]`ها. + لیستی از `[(توکن, توکن), (توکن, توکن), …]`ها. """ diff --git a/hazm/TreebankReader.py b/hazm/TreebankReader.py index bdfac3e7..d03fe85f 100644 --- a/hazm/TreebankReader.py +++ b/hazm/TreebankReader.py @@ -1,19 +1,18 @@ -# coding: utf-8 - """این ماژول شامل کلاس‌ها و توابعی برای خواندن پیکرهٔ تری‌بانک است. پیکرهٔ تری‌بانک حاوی هزاران جملهٔ برچسب‌خورده با اطلاعات نحوی و ساخت‌واژی است. """ -from __future__ import unicode_literals, print_function + import os, sys, re, codecs +from typing import Iterator from xml.dom import minidom from nltk.tree import Tree from .WordTokenizer import WordTokenizer -def coarse_pos_e(tags): +def coarse_pos_e(tags: list[str]) -> list[str]: """برچسب‌های ریز را به برچسب‌های درشت (coarse-grained pos tags) تبدیل می‌کند. Examples: @@ -21,10 +20,10 @@ def coarse_pos_e(tags): 'N' Args: - tags(List[str]): لیست برچسب‌های ریز. + tags: لیست برچسب‌های ریز. Returns: - (List[str]): لیست برچسب‌های درشت. + لیست برچسب‌های درشت. """ @@ -65,15 +64,15 @@ class TreebankReader: """این کلاس شامل توابعی برای خواندن پیکرهٔ تری‌بانک است. Args: - root (str): مسیر فولدر حاوی فایل‌های پیکره - pos_map (str, optional): دیکشنری مبدل برچسب‌های ریز به درشت. - join_clitics (bool, optional): اگر `True‍` باشد واژه‌بست‌ها را به کلمهٔ مادر می‌چسباند. - join_verb_parts (bool, optional): اگر `True` باشد افعال چندبخشی را با _ به هم می‌چسباند. + root: مسیر فولدر حاوی فایل‌های پیکره + pos_map: دیکشنری مبدل برچسب‌های ریز به درشت. + join_clitics: اگر `True‍` باشد واژه‌بست‌ها را به کلمهٔ مادر می‌چسباند. + join_verb_parts: اگر `True` باشد افعال چندبخشی را با _ به هم می‌چسباند. """ def __init__( - self, root, pos_map=coarse_pos_e, join_clitics=False, join_verb_parts=False + self, root:str, pos_map:str=coarse_pos_e, join_clitics:bool=False, join_verb_parts:bool=False ): self._root = root self._pos_map = pos_map if pos_map else lambda tags: ",".join(tags) @@ -81,17 +80,17 @@ def __init__( self._join_verb_parts = join_verb_parts self._tokenizer = WordTokenizer() - def docs(self): + def docs(self) -> Iterator[str]: """اسناد موجود در پیکره را برمی‌گرداند. Yields: - (str): سند بعدی. + سند بعدی. """ for root, dirs, files in os.walk(self._root): for name in sorted(files): try: - with codecs.open( + with open( os.path.join(root, name), encoding="utf8" ) as treebank_file: raw = re.sub(r"\n *", "", treebank_file.read()) @@ -99,7 +98,7 @@ def docs(self): except Exception as e: print("error in reading", name, e, file=sys.stderr) - def trees(self): + def trees(self) -> Iterator[str]: """ساختارهای درختی موجود در پیکره را برمی‌گرداند. Examples: @@ -114,7 +113,7 @@ def trees(self): (PUNC ./PUNC)) Yields: - (str): ساختار درختی بعدی. + ساختار درختی بعدی. """ @@ -173,7 +172,7 @@ def clitic_join(tree, clitic): for child in childs: if not len(child.childNodes): childs.remove(child) - tree = Tree(node.tagName, map(traverse, childs)) + tree = Tree(node.tagName, list(map(traverse, childs))) if ( self._join_clitics and len(tree) > 1 @@ -233,7 +232,7 @@ def clitic_join(tree, clitic): ss = traverse(S) yield traverse(S) - def sents(self): + def sents(self) -> Iterator[list[tuple[str,str]]]: """جملات را به شکل مجموعه‌ای از `(توکن،برچسب)`ها برمی‌گرداند. Examples: @@ -242,13 +241,13 @@ def sents(self): [('دنیای', 'Ne'), ('آدولف', 'N'), ('بورن', 'N'), ('دنیای', 'Ne'), ('اتفاقات', 'Ne'), ('رویایی', 'AJ'), ('است', 'V'), ('.', 'PUNC')] Yields: - (List[Tuple[str,str]]): جملهٔ بعدی. + جملهٔ بعدی. """ for tree in self.trees(): yield tree.leaves() - def chunked_trees(self): + def chunked_trees(self) -> Iterator[str]: """ساختار درختی را به شکل تقطیع شده برمی‌گرداند. Examples: @@ -258,7 +257,7 @@ def chunked_trees(self): '[دنیای آدولف بورن NP] [دنیای اتفاقات رویایی NP] [است VP] .' Yields: - (str): درخت تقطیع شدهٔ بعدی. + درخت تقطیع شدهٔ بعدی. """ collapse = lambda node, label: Tree( diff --git a/hazm/VerbValencyReader.py b/hazm/VerbValencyReader.py index c855b598..484c2509 100644 --- a/hazm/VerbValencyReader.py +++ b/hazm/VerbValencyReader.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """این ماژول شامل کلاس‌ها و توابعی برای خواندن پیکرهٔ ظرفیت نحوی افعال فارسی است. @@ -19,11 +17,11 @@ """ -from __future__ import unicode_literals -import codecs -from collections import namedtuple +from collections import namedtuple +from typing import Iterator + Verb = namedtuple( "Verb", ( @@ -41,21 +39,21 @@ class VerbValencyReader: """این کلاس شامل توابعی برای خواندن پیکرهٔ ظرفیت نحوی افعال فارسی است. Args: - valency_file(str, optional): مسیر فایلِ پیکره. + valency_file: مسیر فایلِ پیکره. """ - def __init__(self, valency_file="corpora/valency.txt"): + def __init__(self, valency_file:str="corpora/valency.txt"): self._valency_file = valency_file - def verbs(self): + def verbs(self) -> Iterator[str]: """افعال پیکره را برمی‌گرداند. Yields: (str): فعل بعدی. """ - with codecs.open(self._valency_file, encoding="utf-8") as valency_file: + with open(self._valency_file, encoding="utf-8") as valency_file: for line in valency_file: if "بن ماضی" in line: continue diff --git a/hazm/WikiExtractor.py b/hazm/WikiExtractor.py index 3be173e3..40a198de 100755 --- a/hazm/WikiExtractor.py +++ b/hazm/WikiExtractor.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """این ماژول شامل توابعی برای استخراج متن به‌شکل تمیز از دیتابیس خام ویکی‌پدیا است. @@ -62,12 +60,12 @@ """ -from __future__ import unicode_literals, division + import sys import argparse import bz2 -import codecs + import cgi import fileinput import logging @@ -83,15 +81,15 @@ PY2 = sys.version_info[0] == 2 # Python 2.7 compatibiity if PY2: - from urllib import quote - from htmlentitydefs import name2codepoint - from itertools import izip as zip, izip_longest as zip_longest + from urllib.parse import quote + from html.entities import name2codepoint + from itertools import zip_longest as zip_longest range = xrange # Use Python 3 equivalent - chr = unichr # Use Python 3 equivalent - text_type = unicode + chr = chr # Use Python 3 equivalent + text_type = str - class SimpleNamespace(object): + class SimpleNamespace: def __init__(self, **kwargs): self.__dict__.update(kwargs) @@ -221,11 +219,11 @@ def __eq__(self, other): ## # Keys for Template and Module namespaces -templateKeys = set(["10", "828"]) +templateKeys = {"10", "828"} ## # Regex for identifying disambig pages -filter_disambig_page_pattern = re.compile("{{disambig(uation)?(\|[^}]*)?}}") +filter_disambig_page_pattern = re.compile(r"{{disambig(uation)?(\|[^}]*)?}}") ## @@ -242,7 +240,7 @@ def keepPage(ns, page): def get_url(uid): - return "%s?curid=%s" % (options.urlbase, uid) + return "{}?curid={}".format(options.urlbase, uid) # ========================================================================= @@ -349,7 +347,7 @@ def fixup(m): except: return text # leave as is - return re.sub("&#?(\w+);", fixup, text) + return re.sub(r"&#?(\w+);", fixup, text) # Match HTML comments @@ -379,12 +377,12 @@ def ignoreTag(tag): placeholder_tag_patterns = [ ( re.compile( - r"<\s*%s(\s*| [^>]+?)>.*?<\s*/\s*%s\s*>" % (tag, tag), + r"<\s*{}(\s*| [^>]+?)>.*?<\s*/\s*{}\s*>".format(tag, tag), re.DOTALL | re.IGNORECASE, ), repl, ) - for tag, repl in placeholder_tags.items() + for tag, repl in list(placeholder_tags.items()) ] # Match preformatted lines @@ -469,7 +467,7 @@ def subst(self, params, extractor, depth): return self -class TemplateArg(object): +class TemplateArg: """ parameter to a template. Has a name and a default value, both of which are Templates. @@ -499,7 +497,7 @@ def __init__(self, parameter): def __str__(self): if self.default: - return "{{{%s|%s}}}" % (self.name, self.default) + return "{{{{{{{}|{}}}}}}}".format(self.name, self.default) else: return "{{{%s}}}" % self.name @@ -526,7 +524,7 @@ def subst(self, params, extractor, depth): return res -class Frame(object): +class Frame: def __init__(self, title="", args=[], prev=None): self.title = title self.args = args @@ -545,7 +543,7 @@ def __str__(self): while prev: if res: res += ", " - res += "(%s, %s)" % (prev.title, prev.args) + res += "({}, {})".format(prev.title, prev.args) prev = prev.prev return "" @@ -555,7 +553,7 @@ def __str__(self): substWords = "subst:|safesubst:" -class Extractor(object): +class Extractor: """ An extraction task on a article. @@ -604,14 +602,14 @@ def write_output(self, out, text): out.write("\n") else: if options.print_revision: - header = '\n' % ( + header = '\n'.format( self.id, self.revid, url, self.title, ) else: - header = '\n' % ( + header = '\n'.format( self.id, url, self.title, @@ -837,8 +835,8 @@ def clean(self, text): text = text.replace("\t", " ") text = spaces.sub(" ", text) text = dots.sub("...", text) - text = re.sub(" (,:\.\)\]»)", r"\1", text) - text = re.sub("(\[\(«) ", r"\1", text) + text = re.sub(r" (,:\.\)\]»)", r"\1", text) + text = re.sub(r"(\[\(«) ", r"\1", text) text = re.sub( r"\n\W+?\n", "\n", text, flags=re.U ) # lines with only punctuations @@ -1277,8 +1275,8 @@ def findMatchingBraces(text, ldelim=0): reOpen = re.compile("[{]{%d,}" % ldelim) # at least ldelim reNext = re.compile("[{]{2,}|}{2,}") # at least 2 else: - reOpen = re.compile("{{2,}|\[{2,}") - reNext = re.compile("{{2,}|}{2,}|\[{2,}|]{2,}") # at least 2 + reOpen = re.compile(r"{{2,}|\[{2,}") + reNext = re.compile(r"{{2,}|}{2,}|\[{2,}|]{2,}") # at least 2 cur = 0 while True: @@ -1618,7 +1616,7 @@ def toRoman(n, romanNumeralMap): # variables -class MagicWords(object): +class MagicWords: """ One copy in each Extractor. @@ -1906,7 +1904,7 @@ def sharp_ifeq(extr, lvalue, rvalue, valueIfTrue, valueIfFalse=None, *args): def sharp_iferror(extr, test, then="", Else=None, *args): if re.match( - '<(?:strong|span|p|div)\s(?:[^\s>]*\s+)*?class="(?:[^"\s>]*\s+)*?error(?:\s[^">]*)?"', + r'<(?:strong|span|p|div)\s(?:[^\s>]*\s+)*?class="(?:[^"\s>]*\s+)*?error(?:\s[^">]*)?"', test, ): return extr.expand(then.strip()) @@ -2078,7 +2076,7 @@ def define_template(title, page): return # check for redirects - m = re.match("#REDIRECT.*?\[\[([^\]]*)]]", page[0], re.IGNORECASE) + m = re.match(r"#REDIRECT.*?\[\[([^\]]*)]]", page[0], re.IGNORECASE) if m: options.redirects[title] = m.group(1) # normalizeTitle(m.group(1)) return @@ -2516,7 +2514,7 @@ def makeInternalLink(title, label): if colon2 > 1 and title[colon + 1 : colon2] not in options.acceptedNamespaces: return "" if options.keepLinks: - return '%s' % (quote(title.encode("utf-8")), label) + return '{}'.format(quote(title.encode("utf-8")), label) else: return label @@ -2566,7 +2564,7 @@ def makeInternalLink(title, label): EXT_LINK_URL_CLASS = r'[^][<>"\x00-\x20\x7F\s]' ANCHOR_CLASS = r"[^][\x00-\x08\x0a-\x1F]" ExtLinkBracketedRegex = re.compile( - "\[(((?i)" + r"\[(((?i)" + "|".join(wgUrlProtocols) + ")" + EXT_LINK_URL_CLASS @@ -2632,14 +2630,14 @@ def makeExternalLink(url, anchor): """ if options.keepLinks: - return '%s' % (quote(url.encode("utf-8")), anchor) + return '{}'.format(quote(url.encode("utf-8")), anchor) else: return anchor def makeExternalImage(url, alt=""): if options.keepLinks: - return '%s' % (url, alt) + return '{}'.format(url, alt) else: return alt @@ -2647,7 +2645,7 @@ def makeExternalImage(url, alt=""): # ---------------------------------------------------------------------- # match tail after wikilink -tailRE = re.compile("\w+") +tailRE = re.compile(r"\w+") syntaxhighlight = re.compile( "<syntaxhighlight .*?>(.*?)</syntaxhighlight>", re.DOTALL @@ -2806,7 +2804,7 @@ def handle_unicode(entity): # Output -class NextFile(object): +class NextFile: """ Synchronous generation of next available file name. @@ -2841,7 +2839,7 @@ def _filepath(self): return "%s/wiki_%02d" % (self._dirname(), self.file_index) -class OutputSplitter(object): +class OutputSplitter: """ File-like object, that splits output to multiple files of a given max size. @@ -2897,7 +2895,7 @@ def load_templates(file, output_file=None): options.modulePrefix = options.moduleNamespace + ":" if output_file: - output = codecs.open(output_file, "wb", "utf-8") + output = open(output_file, "wb", "utf-8") for page_count, page_data in enumerate(pages_from(file)): id, revid, title, ns, page = page_data if not output_file and ( diff --git a/hazm/WikipediaReader.py b/hazm/WikipediaReader.py index d0a943a3..9a8ad33f 100644 --- a/hazm/WikipediaReader.py +++ b/hazm/WikipediaReader.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """این ماژول شامل کلاس‌ها و توابعی برای خواندن پیکرهٔ ویکی‌پدیا است. [پیکرهٔ ویکی‌پدیا](http://download.wikimedia.org/fawiki/latest/fawiki-latest- @@ -10,27 +8,28 @@ """ -from __future__ import unicode_literals, print_function + import os, re, subprocess +from typing import Iterator class WikipediaReader: """این کلاس شامل توابعی برای خواندن پیکرهٔ ویکی‌پدیا است. Args: - fawiki_dump (str): مسیر فولدر حاوی فایل‌های پیکره. - n_jobs (int, optional): تعداد هسته‌های پردازنده برای پردازش موازی. + fawiki_dump: مسیر فولدر حاوی فایل‌های پیکره. + n_jobs: تعداد هسته‌های پردازنده برای پردازش موازی. """ - def __init__(self, fawiki_dump, n_jobs=2): + def __init__(self, fawiki_dump:str, n_jobs:int=2): self.fawiki_dump = fawiki_dump self.wiki_extractor = os.path.join( os.path.abspath(os.path.dirname(__file__)), "WikiExtractor.py" ) self.n_jobs = n_jobs - def docs(self): + def docs(self) -> Iterator[dict]: """مقالات را برمی‌گرداند. هر مقاله، شی‌ای متشکل از چند پارامتر است: @@ -74,11 +73,11 @@ def docs(self): del doc[1] id, url, title = doc_pattern.match(doc[0]).groups() html = "\n".join(doc[1:-1]) - + yield {"id": id, "url": url, "title": title, "html": html, "text": html} doc = [] - def texts(self): + def texts(self) -> Iterator[str]: """فقط متن مقالات را برمی‌گرداند. این تابع صرفاً برای راحتی بیشتر تهیه شده وگرنه با همان تابع @@ -90,7 +89,7 @@ def texts(self): >>> next(wikipedia.texts())[:30] Yields: - (str): متنِ مقالهٔ بعدی. + متنِ مقالهٔ بعدی. """ for doc in self.docs(): diff --git a/hazm/WordTokenizer.py b/hazm/WordTokenizer.py index 5c235bd7..38db82b1 100644 --- a/hazm/WordTokenizer.py +++ b/hazm/WordTokenizer.py @@ -1,5 +1,3 @@ -# coding: utf-8 - """این ماژول شامل کلاس‌ها و توابعی برای استخراج کلماتِ متن است. برای استخراج جملات، از تابع [SentenceTokenizer()][hazm.SentenceTokenizer] @@ -7,9 +5,9 @@ """ -from __future__ import unicode_literals + import re -import codecs + from .utils import words_list, default_words, default_verbs from nltk.tokenize.api import TokenizerI @@ -18,35 +16,35 @@ class WordTokenizer(TokenizerI): """این کلاس شامل توابعی برای استخراج کلماتِ متن است. Args: - words_file (str, optional): مسیر فایل حاوی لیست کلمات. + words_file: مسیر فایل حاوی لیست کلمات. هضم به صورت پیش‌فرض فایلی برای این منظور در نظر گرفته است؛ با این حال شما می‌توانید فایل موردنظر خود را معرفی کنید. برای آگاهی از ساختار این فایل به فایل پیش‌فرض مراجعه کنید. - verbs_file (str, optional): مسیر فایل حاوی افعال. + verbs_file: مسیر فایل حاوی افعال. هضم به صورت پیش‌فرض فایلی برای این منظور در نظر گرفته است؛ با این حال شما می‌توانید فایل موردنظر خود را معرفی کنید. برای آگاهی از ساختار این فایل به فایل پیش‌فرض مراجعه کنید. - join_verb_parts (bool, optional): اگر `True` باشد افعال چندبخشی را با خط زیر به هم می‌چسباند؛ مثلاً «گفته شده است» را به صورت «گفته_شده_است» برمی‌گرداند. - separate_emoji (bool, optional): اگر `True` باشد اموجی‌ها را با یک فاصله از هم جدا می‌کند. - replace_links (bool, optional): اگر `True` باشد لینک‌ها را با کلمهٔ `LINK` جایگزین می‌کند. - replace_IDs (bool, optional): اگر `True` باشد شناسه‌ها را با کلمهٔ `ID` جایگزین می‌کند. - replace_emails (bool, optional): اگر `True` باشد آدرس‌های ایمیل را با کلمهٔ `EMAIL‍` جایگزین می‌کند. - replace_numbers (bool, optional): اگر `True` باشد اعداد اعشاری را با`NUMF` و اعداد صحیح را با` NUM` جایگزین می‌کند. در اعداد غیراعشاری، تعداد ارقام نیز جلوی `NUM` می‌آید. - replace_hashtags (bool, optional): اگر `True` باشد علامت `#` را با `TAG` جایگزین می‌کند. + join_verb_parts: اگر `True` باشد افعال چندبخشی را با خط زیر به هم می‌چسباند؛ مثلاً «گفته شده است» را به صورت «گفته_شده_است» برمی‌گرداند. + separate_emoji: اگر `True` باشد اموجی‌ها را با یک فاصله از هم جدا می‌کند. + replace_links اگر `True` باشد لینک‌ها را با کلمهٔ `LINK` جایگزین می‌کند. + replace_IDs: اگر `True` باشد شناسه‌ها را با کلمهٔ `ID` جایگزین می‌کند. + replace_emails: اگر `True` باشد آدرس‌های ایمیل را با کلمهٔ `EMAIL‍` جایگزین می‌کند. + replace_numbers: اگر `True` باشد اعداد اعشاری را با`NUMF` و اعداد صحیح را با` NUM` جایگزین می‌کند. در اعداد غیراعشاری، تعداد ارقام نیز جلوی `NUM` می‌آید. + replace_hashtags: اگر `True` باشد علامت `#` را با `TAG` جایگزین می‌کند. """ def __init__( self, - words_file=default_words, - verbs_file=default_verbs, - join_verb_parts=True, - separate_emoji=False, - replace_links=False, - replace_IDs=False, - replace_emails=False, - replace_numbers=False, - replace_hashtags=False, + words_file:str=default_words, + verbs_file:str=default_verbs, + join_verb_parts:bool=True, + separate_emoji:bool=False, + replace_links:bool=False, + replace_IDs:bool=False, + replace_emails:bool=False, + replace_numbers:bool=False, + replace_hashtags:bool=False, ): self._join_verb_parts = join_verb_parts self.separate_emoji = separate_emoji @@ -57,7 +55,7 @@ def __init__( self.replace_hashtags = replace_hashtags self.pattern = re.compile( - r'([؟!\?]+|[\d\.:]+|[:\.،؛»\]\)\}"«\[\(\{/\\])' + r'([؟!?]+|[\d.:]+|[:.،؛»\])}"«\[({/\\])' ) # TODO \d self.emoji_pattern = re.compile( "[" @@ -68,28 +66,28 @@ def __init__( flags=re.UNICODE, ) self.emoji_repl = r"\g<0> " - self.id_pattern = re.compile(r"(? list[str]: """توکن‌های متن را استخراج می‌کند. Examples: @@ -264,10 +258,10 @@ def tokenize(self, text): دیگه میخوام ترک تحصیل کنم 😂 😂 😂 Args: - text (str): متنی که باید توکن‌های آن استخراج شود. + text: متنی که باید توکن‌های آن استخراج شود. Returns: - (List[str]): لیست توکن‌های استخراج‌شده. + لیست توکن‌های استخراج‌شده. """ # >>> tokenizer.tokenize('نسخه 0.5 در ساعت 22:00 تهران،1396.') @@ -297,7 +291,7 @@ def tokenize(self, text): tokens = self.join_verb_parts(tokens) return tokens - def join_verb_parts(self, tokens): + def join_verb_parts(self, tokens:list[str]) -> list[str]: """افعال چندبخشی را به هم می‌چسباند. Examples: @@ -314,10 +308,10 @@ def join_verb_parts(self, tokens): ['خسته', 'شدید'] Args: - tokens (List[str]): لیست کلمات یک فعل چندبخشی. + tokens: لیست کلمات یک فعل چندبخشی. Returns: - (List[str]): لیست از افعال چندبخشی که در صورت لزوم بخش‌های آن با کاراکتر خط زیر به هم چسبانده_شده_است. + لیست از افعال چندبخشی که در صورت لزوم بخش‌های آن با کاراکتر خط زیر به هم چسبانده_شده_است. """ diff --git a/hazm/__init__.py b/hazm/__init__.py index 9e7d3baf..94e698d9 100644 --- a/hazm/__init__.py +++ b/hazm/__init__.py @@ -23,10 +23,8 @@ from .Chunker import Chunker, RuleBasedChunker, tree2brackets from .DependencyParser import DependencyParser, MaltParser, TurboParser - from .utils import words_list, stopwords_list - def sent_tokenize(text): if not hasattr(sent_tokenize, "tokenizer"): sent_tokenize.tokenizer = SentenceTokenizer() @@ -36,4 +34,4 @@ def sent_tokenize(text): def word_tokenize(sentence): if not hasattr(word_tokenize, "tokenizer"): word_tokenize.tokenizer = WordTokenizer() - return word_tokenize.tokenizer.tokenize(sentence) + return word_tokenize.tokenizer.tokenize(sentence) \ No newline at end of file diff --git a/hazm/utils.py b/hazm/utils.py index b07df0fa..3a0357d4 100644 --- a/hazm/utils.py +++ b/hazm/utils.py @@ -1,11 +1,9 @@ -# coding: utf-8 - """این ماژول شامل کلاس‌ها و توابع کمکی است. """ import re -import sys, codecs +import sys from os import path PY2 = sys.version_info[0] == 2 @@ -19,10 +17,10 @@ NUMBERS = "۰۱۲۳۴۵۶۷۸۹" -maketrans = lambda A, B: dict((ord(a), b) for a, b in zip(A, B)) +maketrans = lambda A, B: {ord(a): b for a, b in zip(A, B)} -def words_list(words_file=default_words): +def words_list(words_file:str=default_words) -> list[tuple[str, int, tuple[str, ...]]]: """لیست کلمات را برمی‌گرداند. Examples: @@ -31,13 +29,13 @@ def words_list(words_file=default_words): ('آب', 549005877, ('N', 'AJ')) #(id, word, (tag1, tag2, ...)) Args: - words_file (str, optional): مسیر فایل حاوی کلمات. + words_file: مسیر فایل حاوی کلمات. Returns: - (Tuple[str,str,Tuple[str,str]]): فهرست کلمات. + فهرست کلمات. """ - with codecs.open(words_file, encoding="utf-8") as words_file: + with open(words_file, encoding="utf-8") as words_file: items = [line.strip().split("\t") for line in words_file] return [ (item[0], int(item[1]), tuple(item[2].split(","))) @@ -46,7 +44,7 @@ def words_list(words_file=default_words): ] -def stopwords_list(stopwords_file=default_stopwords): +def stopwords_list(stopwords_file:str=default_stopwords) -> list[str]: """لیست ایست‌واژه‌ها را برمی‌گرداند. Examples: @@ -55,22 +53,22 @@ def stopwords_list(stopwords_file=default_stopwords): ['محسوب', 'اول', 'بسیار', 'طول'] Args: - stopwords_file (str, optional): مسیر فایل حاوی ایست‌واژه‌ها. + stopwords_file: مسیر فایل حاوی ایست‌واژه‌ها. Returns: - (List[str]): فهرست ایست‌واژه‌ها. + فهرست ایست‌واژه‌ها. """ - with codecs.open(stopwords_file, encoding="utf8") as stopwords_file: - return list(set(map(lambda w: w.strip(), stopwords_file))) + with open(stopwords_file, encoding="utf8") as stopwords_file: + return list({w.strip() for w in stopwords_file}) def verbs_list(): - with codecs.open(default_verbs, encoding="utf8") as verbs_file: - list = [] + with open(default_verbs, encoding="utf8") as verbs_file: + lst = [] for line in verbs_file: - list.append(line.strip()) - return list + lst.append(line.strip()) + return lst def past_roots(): @@ -95,102 +93,3 @@ def regex_replace(patterns, text): for pattern, repl in patterns: text = re.sub(pattern, repl, text) return text - - -""" -def generate_all_verb_forms(ri, rii): -list = [] -return [ -ri+"م", -ri + "ی", -ri, -ri + "یم", -ri + "ید", -ri + "ند", - -ri + "ه‌ام", -ri + "ه‌ای", -ri + "ه است", -ri + "ه‌ایم", -ri + "ه‌اید", -ri + "ه‌اند", - -"می‌"+ri+"م", -"می‌"+ri + "ی", -"می‌"+ri, -"می‌"+ri + "یم", -"می‌"+ri + "ید", -"می‌"+ri + "ند", - -"می‌"+ri+"ه‌ام", -"می‌"+ri+"ه‌ای", -"می‌"+ri+"ه است", -"می‌"+ri+"ه‌ایم", -"می‌"+ri+"ه‌اید", -"می‌"+ri+"ه‌اند", - -ri+"ه بودم", -ri+"ه بودی", -ri+"ه بود", -ri+"ه بودیم", -ri+"ه بودید", -ri+"ه بودند", - -ri+"ه بوده‌ام", -ri+"ه بوده‌ای", -ri+"ه بوده است", -ri+"ه بوده‌ایم", -ri+"ه بوده‌اید", -ri+"ه بوده‌اند", - -ri+"ه باشم", -ri+"ه باشی", -ri+"ه باشد", -ri+"ه باشیم", -ri+"ه باشید", -ri+"ه باشند", - -"داشتم "+"می‌"+ri+"م", -"داشتی "+"می‌"+ri+"ی", -"داشت "+"می‌"+ri, -"داشتیم "+"می‌"+ri+"یم", -"داشتید "+"می‌"+ri+"ید", -"داشتند "+"می‌"+ri+"ند", - -"داشته‌ام "+"می‌"+ri+"ه‌ام", -"داشته‌ای "+"می‌"+ri+"ه‌ای", -"داشته است "+"می‌"+ri+"ه است", -"داشته‌ایم "+"می‌"+ri+"ه ایم", -"داشته‌اید "+"می‌"+ri+"ه‌اید", -"داشته‌اند "+"می‌"+ri+"ه‌اند", - -"می‌"+rii+"م", -"می‌"+rii+"ی", -"می‌"+rii+"د", -"می‌"+rii+"یم", -"می‌"+rii+"ید", -"می‌"+rii+"ند", - -"ب"+rii+"م", -"ب"+rii+"ی", -"ب"+rii+"د", -"ب"+rii+"یم", -"ب"+rii+"ید", -"ب"+rii+"ند", - -"دارم می‌"+rii+"م", -"داری می‌"+rii+"ی", -"دارد می‌"+rii+"د", -"داریم می‌"+rii+"یم", -"دارید می‌"+rii+"ید", -"دارند می‌"+rii+"ند", - -"خواهم "+ri, -"خواهی "+ri, -"خواهد "+ri, -"خواهیم "+ri, -"خواهید "+ri, -"خواهند "+ri, -] - -""" diff --git a/requirements.txt b/requirements.txt index cecd5455..25588f90 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,4 +3,8 @@ mkdocstrings-python==0.7.1 mkdocstrings==0.19.0 mkdocs-material==8.4.1 mkdocs-glightbox==0.2.0 -pymdown-extensions==9.5 \ No newline at end of file +pymdown-extensions==9.5 +nltk~=3.8.1 +setuptools~=65.5.0 +gensim~=4.3.1 +numpy~=1.24.2 \ No newline at end of file diff --git a/setup.py b/setup.py index c5ccbb67..5184e33b 100755 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -import codecs + from os import path from setuptools import setup @@ -10,7 +10,7 @@ author="Alireza Nourian", author_email="az.nourian@gmail.com", url="https://www.roshan-ai.ir/hazm/", - long_description=codecs.open( + long_description=open( path.join(path.abspath(path.dirname(__file__)), "README.md"), encoding="utf-8" ).read(), long_description_content_type="text/markdown", @@ -18,15 +18,12 @@ package_data={"hazm": ["data/*.dat"]}, classifiers=[ "Topic :: Text Processing", - "Natural Language :: Persian", - "Programming Language :: Python :: 2.7", - "Programming Language :: Python :: 3.4", - "Programming Language :: Python :: 3.5", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", + "Natural Language :: Persian", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", "License :: OSI Approved :: MIT License", ], - install_requires=["nltk==3.4", 'libwapiti>=0.2.1;platform_system!="Windows"'], + install_requires=["nltk==3.8.1", 'libwapiti>=0.2.1;platform_system!="Windows"'], extras_require={"wapiti": ["libwapiti>=0.2.1"]}, ) diff --git a/tests.py b/tests.py index b6926093..ec181813 100644 --- a/tests.py +++ b/tests.py @@ -1,6 +1,3 @@ -# coding: utf-8 - -from __future__ import unicode_literals import sys, inspect, doctest, unittest from hazm import * @@ -30,7 +27,6 @@ # "treebank": TreebankReader, } - class UnicodeOutputChecker(doctest.OutputChecker): def check_output(self, want, got, optionflags): try: @@ -44,7 +40,7 @@ def check_output(self, want, got, optionflags): except: pass - if type(want) == unicode: + if type(want) == str: want = want.replace("٫", ".") # eval issue return want == got @@ -56,14 +52,11 @@ def check_output(self, want, got, optionflags): suites = [] checker = UnicodeOutputChecker() if utils.PY2 else None - for name, object in modules.items(): + for name, obj in list(modules.items()): if all_modules or name in sys.argv: suites.append( - doctest.DocTestSuite(inspect.getmodule(object), checker=checker) - ) - - # if not utils.PY2 and all_modules: - # suites.append(doctest.DocFileSuite("README.md")) + doctest.DocTestSuite(inspect.getmodule(obj), checker=checker) + ) failure = False runner = unittest.TextTestRunner(verbosity=2)