Skip to content

رفعِ چند باگِ لبه‌ای و بهبودِ استحکام، کارایی و پوششِ تست#369

Open
Milomilo777 wants to merge 4 commits into
roshan-research:masterfrom
Milomilo777:fix/edge-case-bugs-robustness-and-tests
Open

رفعِ چند باگِ لبه‌ای و بهبودِ استحکام، کارایی و پوششِ تست#369
Milomilo777 wants to merge 4 commits into
roshan-research:masterfrom
Milomilo777:fix/edge-case-bugs-robustness-and-tests

Conversation

@Milomilo777
Copy link
Copy Markdown

خلاصه

این درخواستِ ادغام مجموعه‌ای از باگ‌های لبه‌ای (edge case)، بهبودهای استحکام و کارایی، و افزودنِ تست‌های رگرسیون را در یک شاخه گرد آورده است. همه‌ی تغییرات برای ورودی‌های معمول رفتارحفظ‌کننده و کم‌ریسک‌اند و فقط رفتارِ نادرست در حالت‌های مرزی را اصلاح می‌کنند.

هر اصلاح با تستِ واقعی پوشش داده شده است. کلِ مجموعه‌ی تست سبز است (۲۶۰ تست قبول، ۴ مورد xfail مطابقِ قبل) و اجرای ruff روی کلِ مخزن بدونِ خطاست. تنها ماژولِ تعبیه‌سازی به‌دلیلِ توضیحِ بخشِ «یادداشتِ سازگاری» در محیطِ توسعه اجرا نشد و آگاهانه دست‌نخورده ماند.

باگ‌های منطقی و لبه‌ای رفع‌شده

۱. تهی‌شدنِ خروجیِ ریشه‌یاب — hazm/stemmer.py

  • مشکل: پسوندهای چندنویسه‌ای بدونِ هیچ محافظی حذف می‌شدند؛ پس واژه‌هایی که خودشان یک پسوند بودند به رشته‌ی تهی یا یک‌نویسه فرومی‌پاشیدند (stem('ها')'' و stem('رها')'ر').
  • راه‌حل: افزودنِ محافظی که اگر حذفِ پسوند ریشه‌ای کوتاه‌تر از دو نویسه باقی بگذارد، واژه دست‌نخورده بازگردانده شود. رفتارِ پسوندهای تک‌نویسه‌ایِ پیشین تغییری نمی‌کند.

۲. حذفِ خاموشِ جزءِ پایانیِ فعل — hazm/word_tokenizer.py

  • مشکل: در join_verb_parts، اگر یک «فعلِ کمکیِ پیشین» مانندِ خواهد واپسین توکن می‌بود، در نشانگرِ خالیِ انباشتگر ادغام و سپس دور انداخته می‌شد؛ پس ['کتاب', 'خواهد'] به ['کتاب'] تبدیل می‌شد (از‌دست‌رفتنِ داده).
  • راه‌حل: شاخه‌ی ادغامِ فعلِ پیشین تنها وقتی فعال می‌شود که انباشتگر خالی نباشد.

۳. تزریقِ کلیدهای تهی و تک‌نویسه در واژگانِ افعال — hazm/lemmatizer.py

  • مشکل: مدخلِ بدونِ ریشه‌ی گذشته‌ی #هست صورت‌های صرفیِ تهی و تک‌نویسه می‌ساخت و کلیدهایی مانندِ '' و 'م' را به یک لِمِ فعل نگاشت می‌کرد؛ پس lemmatize('') و lemmatize('م') خروجیِ نادرست می‌دادند.
  • راه‌حل: نادیده‌گرفتنِ صورت‌های کوتاه‌تر از دو نویسه هنگامِ ساختِ واژگان، به‌علاوه‌ی بازگشتِ زودهنگام برای ورودیِ تهی.

۴. خطای اندیس در استخراجِ ویژگیِ برچسب‌زن — hazm/pos_tagger.py

  • مشکل: متدِ features با word[0] و word[-1] روی توکنِ رشته‌ی تهی خطای IndexError می‌داد و کلِ فراخوانیِ tag را می‌شکست. همین متد در Chunker نیز بازاستفاده می‌شود، پس تکه‌بندی هم آسیب می‌دید.
  • راه‌حل: استفاده از برش‌های امنِ word[:1] و word[-1:] که برای توکن‌های معمول خروجیِ یکسان و برای توکنِ تهی رشته‌ی تهی می‌دهند.

۵. تکرارناپذیریِ خوانندگانِ پیکره — arman / naab / ner / pn_summary

  • مشکل: این خوانندگان یک تکرارگرِ یک‌بارمصرفِ Path.glob() را ذخیره می‌کردند؛ پس فراخوانیِ دومِ sents()/docs() هیچ خروجی‌ای نمی‌داد.
  • راه‌حل: مادی‌سازیِ فهرستِ فایل‌ها با sorted(...) برای تکرارپذیری و ترتیبِ قطعی.

۶. خطای نادرست هنگامِ نبودِ فایلِ پیکره — hazm/corpus_readers/degarbayan_reader.py

  • مشکل: شاخه‌ی نبودِ فایل به متغیرِ تعریف‌نشده‌ی e اشاره می‌کرد و به‌جای FileNotFoundError، خطای NameError می‌داد.
  • راه‌حل: حذفِ ارجاع به متغیرِ تعریف‌نشده تا خطای درستِ FileNotFoundError صادر شود.

۷. فراخوانیِ دوباره و ناایمنِ پیمایشِ درخت — hazm/corpus_readers/treebank_reader.py

  • مشکل: تابعِ trees تابعِ traverse را دو بار صدا می‌زد؛ بارِ نخست دور ریخته می‌شد ولی درختِ زنده را تغییر می‌داد و بارِ دوم روی داده‌ی دست‌خورده اجرا می‌شد.
  • راه‌حل: تنها یک بار فراخوانی.

استحکام، کارایی و مستندات

  • جداکننده‌ی اعشار در hazm/constants.py از ردهٔ نویسه‌ی [\d+] (که علامتِ + را هم می‌پذیرفت) به (\d)\.(\d) اصلاح شد تا فقط میانِ ارقام عمل کند.
  • جدول‌های ترجمه‌ی نویسه در hazm/normalizer.py یک‌بار در زمانِ بارگذاریِ ماژول ساخته می‌شوند، نه در هر فراخوانیِ normalize و persian_number (مسیرِ پرتکرارِ کتابخانه).
  • توابعِ past_roots و present_roots در hazm/utils.py دیگر روی مدخلِ بدونِ # خطای اندیس نمی‌دهند.
  • دو docstring نادرست اصلاح شد: نمونه‌ی unicodes_replacement که متدِ دیگری را نشان می‌داد، و توضیحِ پارامترِ replace_numbers.

تست و راستی‌آزمایی

دو فایلِ تستِ جدید افزوده شد:

  • tests/test_regression_fixes.py
  • tests/corpus_readers/test_reader_reiteration.py

این تست‌ها هر یک از اصلاحاتِ بالا را قفل می‌کنند و چند مسیرِ پیش‌تر بدونِ‌تست را نیز پوشش می‌دهند: حالت‌های مرزیِ ریشه‌یاب، توکنایزِ ورودیِ تهی، جای‌گزینیِ ایمیل و هشتگ، شاخه‌های نقشِ دستوریِ لماتایزر، توابعِ کمکی، و تکرارپذیریِ خوانندگانِ پیکره.

هم‌چنین پیکربندیِ pytest اصلاح شد: کلیدهای filterwarnings و گزینه‌ی نمایشِ شناسه‌ی تست که اشتباهاً زیرِ بخشِ [tool.black] قرار داشتند و خاموش بودند، به بخشِ درستِ [tool.pytest.ini_options] منتقل شدند.

یادداشتِ سازگاری

ماژولِ تعبیه‌سازی (embedding) به gensim وابسته است که در محیطِ توسعه (پایتونِ ۳٫۱۴) wheel سازگار نداشت؛ پس برای آنکه فقط تغییراتِ آزموده‌شده ارائه شوند، هیچ تغییری در آن ماژول داده نشد.

موارد شناسایی‌شده برای پیگیریِ بعدی (خارج از دامنه‌ی این درخواست)

این موارد طیِ تحلیل یافت شدند ولی برای محدودنگه‌داشتنِ ریسک و چون آزمونشان به منابعِ سنگین نیاز دارد در این درخواست نیامده‌اند:

  • در embedding.py، متدِ WordEmbedding.train یک generator یک‌بارمصرف را دو بار مصرف می‌کند (یک‌بار build_vocab و یک‌بار train)، پس آموزش روی پیکره‌ی تهی اجرا می‌شود.
  • نبودِ مدیریتِ واژه‌ی خارج‌از‌واژگان در similarity.
  • اجرانشدنِ doctestها در CI (حدودِ ۹۸ مورد مربوط به Conjugation فاقدِ خطِ مقداردهیِ اولیه‌اند).

در صورتِ تمایلِ نگه‌دارندگان، این موارد را با خوشحالی در درخواست‌های جداگانه پیگیری می‌کنم.

🤖 Generated with Claude Code

Milomilo777 and others added 4 commits June 2, 2026 09:01
- Stemmer: stop stripping a multi-character suffix when it would leave an empty or single-character stem (e.g. stem('ها') returned '', stem('رها') returned 'ر'); suffix-only tokens are now returned unchanged.
- WordTokenizer.join_verb_parts: keep a trailing 'before verb' (e.g. 'خواهد') that was silently dropped when it was the last token, and only treat truly blank lines as empty when loading the verbs file.
- Lemmatizer: skip the empty/single-character conjugations produced by the empty past-root entry '#هست', so '' and clitics like 'م' are no longer mapped to a verb lemma; lemmatize('') now returns ''.
- POSTagger.features: use safe slicing (word[:1] / word[-1:]) so an empty-string token no longer raises IndexError (this also fixes Chunker, which reuses it).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- ArmanReader, NaabReader, NerReader and PnSummaryReader stored a one-shot Path.glob() iterator, so a second call to sents()/docs() yielded nothing; the file list is now materialized with sorted(...) for deterministic, repeatable iteration.
- ArmanReader: tolerate runs of whitespace between token and tag via line.split().
- DegarbayanReader: raise FileNotFoundError for a missing corpus file instead of crashing with NameError on an undefined variable.
- TreebankReader.trees: call traverse() once; the duplicate call did dead work and mutated the live DOM twice.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Normalizer: build the character translation tables once at import time instead of rebuilding them on every normalize()/persian_number() call.
- constants: the decimal-separator rule used the character class [\d+], which also matched a literal '+'; it now matches only digits ((\d)\.(\d)).
- utils: past_roots()/present_roots() no longer raise IndexError on a verb entry that lacks '#'.
- Fix the unicodes_replacement docstring (it demonstrated remove_specials_chars) and clarify the replace_numbers parameter docstring.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Move filterwarnings and the test-id escaping option out of the stray [tool.black] table into a real [tool.pytest.ini_options] section (they were silently ignored), and set testpaths.
- Add tests/test_regression_fixes.py and tests/corpus_readers/test_reader_reiteration.py covering every fix above plus previously untested paths (stemmer edge cases, empty-input tokenization, email/hashtag replacement, lemmatizer POS branches, utils helpers, reader re-iteration and missing-file handling).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant