├── .hgignore ├── .hgtags ├── AUTHORS.rst ├── INSTALL ├── LICENSE ├── MANIFEST.in ├── README ├── REQUIREMENTS ├── _runtests_django.py ├── _runtests_nodjango.py ├── bench ├── __init__.py └── text │ ├── um.txt │ └── voice.txt ├── dicts ├── COPYING ├── README ├── converted │ ├── en │ │ └── noremove │ └── ru │ │ └── noremove └── src │ ├── Dicts │ ├── GraphAn │ │ ├── abbr.eng │ │ ├── abbr.ger │ │ ├── abbr.rus │ │ ├── abbrev.log │ │ ├── enames.lem │ │ ├── enames.txt │ │ ├── extensions.txt │ │ ├── idents.txt │ │ ├── keyboard.txt │ │ ├── keyword │ │ ├── ross.txt │ │ └── space.dic │ ├── Morph │ │ ├── Eng │ │ │ └── morph.options │ │ ├── Rus │ │ │ └── morph.options │ │ ├── egramtab.tab │ │ └── rgramtab.tab │ └── SrcMorph │ │ ├── Eng.mwz │ │ ├── EngSrc │ │ └── morphs.mrd │ │ ├── Rus.mwz │ │ └── RusSrc │ │ └── morphs.mrd │ ├── Docs │ └── Morph_UNIX.txt │ └── copying ├── docs ├── CHANGES.rst ├── Makefile ├── algo.rst ├── conf.py ├── index.rst ├── intro.rst ├── make.bat ├── ref │ ├── Morph_UNIX.rst │ ├── gram_info_ru.rst │ ├── index.rst │ ├── morph.rst │ ├── storage_backends.rst │ └── storages.rst └── usage │ ├── base.rst │ ├── contrib.rst │ ├── django.rst │ ├── index.rst │ └── speed.rst ├── encode_dicts.py ├── example.py ├── pymorphy ├── __init__.py ├── _morph.pxd ├── _morph.py ├── backends │ ├── __init__.py │ ├── base.py │ ├── mrd_source.py │ ├── pickle_source.py │ └── shelve_source │ │ ├── __init__.py │ │ ├── cdb_shelve.py │ │ ├── cdblib_shelve.py │ │ ├── pytc_shelve.py │ │ ├── shelf_with_hooks.py │ │ ├── sqlite_shelve.py │ │ └── tinycdb_shelve.py ├── constants.py ├── contrib │ ├── __init__.py │ ├── lastnames_ru.py │ ├── scan.py │ ├── tokenizers.py │ └── word_case.py ├── django_conf.py ├── models.py ├── morph.py ├── morph_tests │ ├── __init__.py │ ├── base.py │ ├── data │ │ ├── __init__.py │ │ └── basic.py │ ├── data_parsing.py │ ├── dicts.py │ ├── dirty.py │ ├── graminfo.py │ ├── hyphen.py │ ├── lastnames_ru.py │ ├── thread_bugs.py │ ├── tokenizers.py │ ├── utilities.py │ └── word_case.py ├── py3k.py ├── templatetags │ ├── __init__.py │ └── pymorphy_tags.py ├── tests.py ├── utils.py └── version.py ├── runtests.sh ├── setup.cfg ├── setup.py ├── speedups ├── INSTALL ├── LICENSE ├── MANIFEST.in ├── README.rst ├── _morph.c ├── pymorphy_speedups │ ├── __init__.py │ └── version.py └── setup.py ├── tox.ini └── update_speedups.sh /.hgignore: -------------------------------------------------------------------------------- 1 | syntax: glob 2 | 3 | #projects 4 | .settings/* 5 | .project 6 | .pydevproject 7 | .cache/* 8 | .idea/* 9 | nbproject/* 10 | .buildpath 11 | build.properties 12 | .idea 13 | 14 | #temp files 15 | *.pyc 16 | *.orig 17 | *~ 18 | 19 | #os files 20 | .DS_Store 21 | Thumbs.db 22 | 23 | #setup 24 | pip-log.txt 25 | build/* 26 | dist/* 27 | MANIFEST 28 | pymorphy.egg-info/ 29 | 30 | #project-specific files 31 | .tox 32 | my_tests.py 33 | stuff 34 | *.sqlite 35 | *.utf8.mrd 36 | *.pickle 37 | *.shelve 38 | *.cdb 39 | *.cdblib 40 | *.tinycdb 41 | *.tcb 42 | *.tch 43 | *.zip 44 | *.tmp 45 | *.log-psyco 46 | docs/_* 47 | bench/do.py 48 | bench/text/war.txt 49 | forum 50 | -------------------------------------------------------------------------------- /.hgtags: -------------------------------------------------------------------------------- 1 | aab8a9f668eb15da8572a2a250d5aa1127de8a00 v0.2.1 2 | df5f205252fd68a4c30a731b19907596e3732db6 v0.3 3 | 2540e46b049a88edc33c3770125bf099edc1c032 v0.3.5 4 | 0e6a40c97cfc159b519bac8262688244dd67530c v0.4 5 | d17a06f4ce7bb94045e73d8a1e1b1ea9e5550229 v0.4.1 6 | 24f9a064df35e7a2bcb6ae8aa6b405d53402f85b v0.4.2 7 | 20c5c9deaa0483fb0359ddda80e1b7f139c4cf6b v0.4.3 8 | 480e2dfa8476511ad3531df35b05a8ba41e73c79 0.5.0 9 | 480e2dfa8476511ad3531df35b05a8ba41e73c79 0.5.0 10 | 0000000000000000000000000000000000000000 0.5.0 11 | 480e2dfa8476511ad3531df35b05a8ba41e73c79 v0.5.0 12 | cfc80a407b13c11df38e55e1f796c0b6c2aad40b v0.5.1 13 | 8cf51c200957bb9493d41abd64e9666d71164fa8 v0.5.2 14 | 8e082f733552baa2ef6a71c878003279f01dcff8 v0.5.3 15 | 06f041b9c3d974c3ff67657031d3cd03c7668e36 v0.5.4 16 | 02eda365e8d995f33ccfe86242890d9fb8f940d3 v0.5.5 17 | f85d1f11a40585b68cf14aad10db09d96d4287ea v0.5.6 18 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | Авторы (в хронологическом порядке) 2 | ================================== 3 | 4 | * Михаил Коробов; 5 | * peterdemin (исправлене функции convert_file); 6 | * Александр Клюев (исправление ошибки с юникодными строками); 7 | * Anonymous? (команда для перевода bsddb-словарей в старый формат); 8 | * delta32 (исправление ошибки совместимости с python 2.5); 9 | * Anonymous? (фильтр inflect разбивал слова по дефису); 10 | * knurt (склонение фамилий - русских, украинских, литовских, 11 | эстонских, части армянских); 12 | * Юрий Бабуров (помощь при ускорении и др.); 13 | * Евгений Пивнев. 14 | 15 | 16 | Благодарности 17 | ============= 18 | 19 | Зализняку Андрею Анатольевичу за словарь. 20 | aot.ru за описание алгоритмов и словари в электронном виде, выложенные в открытый доступ. 21 | Александр Пак написал пример python-кода, который помог в понимании алгоритмов. 22 | Ивану Сагалаеву за совет про переносимый формат хранения данных (sqlite). 23 | Организаторам конференции "Диалог 2010" за приглашение, тестовые данные, 24 | полезное обсуждение и проверку результатов работы pymorphy. 25 | gramota.ru и Н. А. Еськовой за материалы по склонению фамилий. 26 | -------------------------------------------------------------------------------- /INSTALL: -------------------------------------------------------------------------------- 1 | 1. pip install pymorphy 2 | 3 | 2. Download and extract dictionary pack 4 | from http://bitbucket.org/kmike/pymorphy/downloads/ 5 | 6 | 3. Use it 7 | 8 | from pymorphy import get_morph 9 | morph = get_morph('path/to/extracted/files') -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) Mikhail Korobov 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 14 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR 15 | A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 16 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 17 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 18 | OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS.rst 2 | include INSTALL 3 | include docs/Makefile 4 | include docs/make.bat 5 | include docs/conf.py 6 | recursive-include docs *.rst 7 | recursive-include pymorphy/morph_tests *.py 8 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Morphological analyzer (POS tagger / inflection engine) for 2 | Russian and English languages using converted AOT 3 | (http://www.aot.ru/download.php) dictionaries. 4 | 5 | Documentation (mostly in Russian): http://pymorphy.rtfd.org/ 6 | 7 | Author: Mikhail Korobov , License: MIT. 8 | 9 | ------------ 10 | 11 | Морфологический анализатор для русского и английского 12 | (возможно, еще немецкого) языка. 13 | 14 | Документация: http://pymorphy.rtfd.org/ 15 | 16 | Обсуждение: http://groups.google.com/group/pymorphy 17 | 18 | Репозиторий: https://bitbucket.org/kmike/pymorphy/ 19 | -------------------------------------------------------------------------------- /REQUIREMENTS: -------------------------------------------------------------------------------- 1 | simplejson 2 | unittest2 3 | 4 | # для словарей в формате CDB 5 | tinycdb 6 | -------------------------------------------------------------------------------- /_runtests_django.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import sys 4 | from django.conf import settings 5 | from django.core.management import call_command 6 | 7 | sys.path.insert(0, '.') 8 | 9 | settings.configure( 10 | INSTALLED_APPS=('pymorphy',), 11 | PYMORPHY_DICTS = { 12 | 'ru': { 13 | 'dir': 'dicts/converted/ru', 14 | 'backend': 'sqlite', 15 | 'use_cache': True, 16 | 'default': True, 17 | }, 18 | 'en': { 19 | 'dir': 'dicts/converted/en', 20 | }, 21 | }, 22 | DATABASES = {'default': { 23 | 'ENGINE': 'django.db.backends.sqlite3' 24 | }} 25 | ) 26 | 27 | if __name__ == "__main__": 28 | call_command('test', 'pymorphy') 29 | -------------------------------------------------------------------------------- /_runtests_nodjango.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | sys.path.insert(0, '.') 4 | 5 | from pymorphy.morph_tests.graminfo import * 6 | from pymorphy.morph_tests.utilities import * 7 | from pymorphy.morph_tests.base import * 8 | from pymorphy.morph_tests.dirty import * 9 | from pymorphy.morph_tests.hyphen import * 10 | from pymorphy.morph_tests.tokenizers import * 11 | from pymorphy.morph_tests.thread_bugs import * 12 | from pymorphy.morph_tests.lastnames_ru import * 13 | from pymorphy.morph_tests.word_case import * 14 | 15 | if __name__ == '__main__': 16 | unittest2.main() 17 | 18 | -------------------------------------------------------------------------------- /bench/__init__.py: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | from __future__ import absolute_import, unicode_literals, print_function 3 | import cProfile 4 | from datetime import datetime 5 | import re 6 | import os 7 | import codecs 8 | import timeit 9 | 10 | import pymorphy 11 | from pymorphy.contrib.tokenizers import extract_words 12 | 13 | DICT_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), 14 | '..', 'dicts', 'converted')) 15 | 16 | def total_seconds(delta): 17 | return delta.days * 3600 * 24 + delta.seconds + delta.microseconds/100000.0 18 | 19 | 20 | def get_words(text): 21 | # return list(extract_words(text)) 22 | r = re.compile('[\W+-]',re.U) 23 | return [word for word in r.split(text.upper()) if word] 24 | 25 | 26 | def do_normalize(words, morph): 27 | for word in words: 28 | forms = morph.normalize(word) 29 | 30 | 31 | def do_pluralize(words, morph): 32 | for word in words: 33 | forms = morph.pluralize_ru(word) 34 | 35 | 36 | def do_all(words, morph): 37 | do_normalize(words, morph) 38 | # do_pluralize(words, morph) 39 | 40 | 41 | 42 | def load_words(fn): 43 | filename = os.path.abspath(os.path.join('text', fn)) 44 | with codecs.open(filename, encoding='utf-8') as f: 45 | text = f.read() 46 | return get_words(text) 47 | 48 | def get_morph(backend, **kwargs): 49 | if backend == 'pickle': 50 | path = os.path.join(DICT_PATH, 'ru', 'morphs.pickle') 51 | else: 52 | path = os.path.join(DICT_PATH,'ru') 53 | return pymorphy.get_morph(path, backend, **kwargs) 54 | 55 | def get_mem_usage(): 56 | try: 57 | import psutil 58 | proc = psutil.Process(os.getpid()) 59 | return proc.get_memory_info() 60 | except ImportError: 61 | from collections import namedtuple 62 | Info = namedtuple('Info', 'vms rss') 63 | return Info(0, 0) 64 | 65 | def print_memory_usage(old=None): 66 | info = get_mem_usage() 67 | M = 1024*1024.0 68 | 69 | if old: 70 | print("RSS: %0.1fM (+%0.1fM), VMS: %0.1fM (+%0.1fM)" % ( 71 | info.rss/M, (info.rss-old.rss)/M, info.vms/M, (info.vms-old.vms)/M), 72 | ) 73 | else: 74 | print("RSS: %0.1fM, VMS: %0.1fM" % (info.rss/M, info.vms/M)) 75 | 76 | 77 | def bench(filename, backend='shelve', use_psyco=True, use_cache=True, profile=True): 78 | 79 | if profile: 80 | words = load_words(filename) 81 | print ('Text is loaded (%d words)' % len(words)) 82 | usage = get_mem_usage() 83 | 84 | morph = get_morph(backend, cached=use_cache) 85 | 86 | prof = cProfile.Profile() 87 | prof = prof.runctx('do_all(words, morph)', globals = globals(), locals=locals()) 88 | prof.print_stats(1) 89 | print_memory_usage(usage) 90 | else: 91 | # prep = """ 92 | #from bench import do_all, load_words, get_morph 93 | #words = load_words('%s') 94 | #morph = get_morph('%s', cached=%s) 95 | # """ % (file, backend, use_cache) 96 | # res = timeit.timeit('do_all(words, morph)', prep, number=1) 97 | # print '%s => %s (cache: %s) => %.2f sec' % (file, backend, use_cache, res) 98 | 99 | start = datetime.now() 100 | words = load_words(filename) 101 | usage = get_mem_usage() 102 | morph = get_morph(backend, cached=use_cache) 103 | loaded = datetime.now() 104 | do_all(words, morph) 105 | parsed = datetime.now() 106 | 107 | load_time = total_seconds(loaded-start) 108 | parse_time = total_seconds(parsed-loaded) 109 | wps = len(words)/parse_time 110 | 111 | print ('%s => %s (cache: %s) => load: %.2f sec, parse: %0.2f sec (%d words/sec)' % ( 112 | filename, backend, use_cache, load_time, parse_time, wps)) 113 | print_memory_usage(usage) 114 | 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /bench/text/um.txt: -------------------------------------------------------------------------------- 1 | И, кстати, о неком Крихтоне 2 | 3 | Крихтон невероятно умный человек — почти неправдоподобно, сверхъестественно 4 | умный. И не просто сведущий в том или в этом, а вообще образец ума; вам его 5 | никогда не обскакать; он идет по свету и бесцельно рассыпает перлы своего 6 | остроумия. Он побивает вас в шутках, ловит на неточностях и подает ваши лучшие 7 | номера куда тоньше и оригинальней. Истинно воспитанный человек, на столь многое 8 | притязающий, окажется, по крайней мере вам в утешение, уродлив лицом, хил или 9 | несчастлив в браке, но Крихтону и в голову не приходит подобная деликатность. 10 | Он появится в комнате, где вы, скажем, сидите компанией и острите, и начнет 11 | сыпать шутками, пусть и менее забавными, но, бесспорно, более хлёсткими. И  12 | вот вы один за другим умолкаете и, попыхивая трубкой, глядите на него с тоскою 13 | и злобой. Ещё не было случая, чтобы он не обыграл меня в шахматы. Люди говорят 14 | о нем и спрашивают моего мнения, и, если я решаюсь нелестно о нем отозваться, 15 | смотрят так, будто подозревают меня в зависти. Безмерно хвалебные рецензии на  16 | его книги и полотна предстают моим взорам в самых неожиданных местах. Право, 17 | из-за него я почти перестал читать газеты. И однако… 18 | 19 | Подобный ум — ещё не все на свете. Он никогда не пленял меня, и мне часто 20 | думалось, что вообще он не может никого пленить. Допустим, вы сказали что-то 21 | остроумное, произнесли какой-то парадокс, нашли тонкое сравнение или набросали 22 | образную картину; как воспримут это обычные люди? Те, кто глупее вас, люди 23 | нетонкие, заурядные, не посвященные в ваши проблемы, будут попросту раздражены 24 | вашими загадками; те, кто умней, почтут ваше остроумие явной глупостью; ровни  25 | же ваши сами рвутся сострить и, естественно, видят в вас опасного конкурента. 26 | Словом, подобный ум есть не что иное, как чистый эгоизм в его наихудшей и  27 | глупейшей форме. Этот поток остроумия, извергаемый на вас без устали и  28 | сожаления, — неприкрытое хвастовство. Гуляет себе по свету этакий хмельной 29 | раб острословия и сыплет каламбурами. А потом берет те, что получше, и  30 | вставляет в рамку, под стекло. И вот появляется импрессионистская живопись 31 | вроде картин Крихтона, — те же нанесённые на полотна остроты. Они лишены 32 | содержания, и у скромного благомыслящего человека моего типа вызывают приступы 33 | отвращения, точно так же, как фиглярство в литературе. Сюжет здесь не более чем 34 | предлог, на деле это бессмысленная и неприличная самореклама. Такой умник 35 | считает, что возвысится в ваших глазах, если будет беспрестанно вас поражать. 36 | Он и подписи-то не поставит без какой-нибудь особенной завитушки. У него 37 | начисто отсутствует главное свойство джентльмена: умение быть 38 | великодушно-банальным. Я ж… 39 | 40 | Если говорить о личном достоинстве, то юному отпрыску почтенного семейства, 41 | небездарному от природы, не к чему унижаться до подобного кривлянья. 42 | Умничанье — последнее прибежище слабодушных, утеха тщеславного раба. Вы не  43 | можете победить с оружием в руках и не в силах достойно снести второстепенную 44 | роль, и вот себе в утешение вы пускаетесь в эксцентричное штукарство и  45 | истощаете свой мозг острословием. Из всех зверей умнейший — обезьяна, а  46 | сравните ее жалкое фиглярство с царственным величием слона! 47 | И еще, я никак не могу избавиться от мысли, что ум — наибольшая помеха карьере. 48 | Разве приходилось вам видеть, чтобы по-настоящему умный человек занимал важный 49 | пост, пользовался влиянием и чувствовал себя уверенно? Взять, к примеру, хотя  50 | бы Королевскую академию или суд, а то и… Какое там!.. Ведь само понятие разума 51 | означает способность постоянно искать новое, а это есть отрицание всего 52 | устоявшегося. 53 | 54 | Когда Крихтон начинает особенно действовать мне на нервы, обретает новых 55 | поклонников или входит в еще большую славу, я утешаюсь мыслями о дяде Августе. 56 | Это была гордость нашей семьи. Даже тётя Шарлотта произносила его имя с  57 | замиранием в голосе. Он отличался поразительной, я бы даже сказал, исполинской 58 | глупостью, которая прославила его и, что важнее, доставила ему влияние и  59 | богатство. Он был прочен, как египетская пирамида, и от него так же трудно 60 | было ждать, чтоб он хоть капельку сдвинулся с места или сделал что-нибудь 61 | неожиданное. О чем бы ни шла речь, он всегда выказывал полнейшее невежество; 62 | все, что он изрекал своим звучным баритоном, было чудовищно глупо. Он мог — я  63 | не раз был тому свидетелем — сровнять с землей какого-нибудь умника типа 64 | Крихтона своими похожими на трамбовку тяжёлыми, плоскими и увесистыми репликами, 65 | которых было ни отразить, ни избегнуть. Он неизменно побеждал в спорах, хотя 66 | совсем не был остёр на язык. Он просто подминал под себя собеседника. Это 67 | походило на встречу шпажонки с лавиной. Душа его обладала колоссальной инертной 68 | массой. Он не знал волнения, не терял выдержки, не утрачивал сил, он давил — и  69 | все тут. Умные речи разбивались о него, как лёгкие судёнышки о бетонированные 70 | берега. Его точным подобием является его надгробный памятник — массивная глыба 71 | из нетесаного гранита, откровенно безобразная, но видная за милю. Она высится 72 | над лесом крохотных белых символов людской скорби, будто и на кладбище он  73 | подавляет собой целую толпу умников. 74 | 75 | Уверяю вас, разумное есть противоположность великому. Британская империя, 76 | как и Римская, создана тупицами. И не исключено, что умники нас погубят. 77 | Представьте себе полк, состоящий из шутников и оригиналов. Свет ещё не знал 78 | государственного деятеля, который не отличался бы хоть малой толикой глупости, 79 | а гениальность, по-моему, непременно в чем-то сродни божественной простоте. Те, 80 | кого принято называть великими мастерами — Шекспир, Рафаэль, Милтон и  81 | другие, — обладали какой-то особой непосредственностью, неизвестной Крихтону. 82 | Они заметно уступают ему в блеске, и общение с ними не оставляет в душе 83 | тягостного духовного напряжения. Даже Гомер временами клюёт носом. В их  84 | творениях есть пригодные для отдыха места — широкие, овеваемые ветром луговины 85 | и мирные уголки. А вот Крихтон не открывает вашим взорам просторов Тихого 86 | океана; он томит вас бесконечным видом на мыс Горн; всюду хребты да пики, 87 | пики да хребты. 88 | 89 | Пусть Крихтон нынче в моде — мода эта недолговечна. Разумеется, я не желаю 90 | ему зла, и все же не могу отделаться от мысли, что конец его близок. Наверно, 91 | эпоха умничанья переживает свой последний расцвет. Люди давно уже мечтают о  92 | покое. Скоро заурядного человека будут разыскивать, как тенистый уголок на  93 | измученной зноем земле. Заурядность станет новым видом гениальности. «Дайте 94 | нам книги без затей, — потребуют люди, — и самые что ни на есть успокоительные, 95 | плоские шедевры. Мы устали, смертельно устали!». Кончится этот лихорадочный и  96 | мучительный период постоянного напряжения, а с ним исчезнет и литература 97 | fin du siecle’а, декаданса и прочее, прочее. И тогда подымет голову круглолицая 98 | и заспанная литература, литература огромной цели и крупной формы, полнотелая и  99 | спокойная. Крихтона запишут в классики, господа Мади будут со скидкой продавать 100 | его не нашедшие спроса произведения, и я перестану терзаться его тошнотворным 101 | успехом. -------------------------------------------------------------------------------- /dicts/README: -------------------------------------------------------------------------------- 1 | Dictionaries in "src" folder are the ones found on http://www.aot.ru/download.php 2 | You should convert and encode them using encode_dicts.py utility: 3 | 4 | python encode_dicts.py 5 | 6 | This can take a long time depending on your hardware. 7 | 8 | See copying for details about their license (LGPL). 9 | -------------------------------------------------------------------------------- /dicts/converted/en/noremove: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmike/pymorphy/9a6614449447c6ceee35e6d0dfe36903ebc80c5f/dicts/converted/en/noremove -------------------------------------------------------------------------------- /dicts/converted/ru/noremove: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmike/pymorphy/9a6614449447c6ceee35e6d0dfe36903ebc80c5f/dicts/converted/ru/noremove -------------------------------------------------------------------------------- /dicts/src/Dicts/GraphAn/abbr.eng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmike/pymorphy/9a6614449447c6ceee35e6d0dfe36903ebc80c5f/dicts/src/Dicts/GraphAn/abbr.eng -------------------------------------------------------------------------------- /dicts/src/Dicts/GraphAn/abbr.ger: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmike/pymorphy/9a6614449447c6ceee35e6d0dfe36903ebc80c5f/dicts/src/Dicts/GraphAn/abbr.ger -------------------------------------------------------------------------------- /dicts/src/Dicts/GraphAn/abbr.rus: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmike/pymorphy/9a6614449447c6ceee35e6d0dfe36903ebc80c5f/dicts/src/Dicts/GraphAn/abbr.rus -------------------------------------------------------------------------------- /dicts/src/Dicts/GraphAn/abbrev.log: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmike/pymorphy/9a6614449447c6ceee35e6d0dfe36903ebc80c5f/dicts/src/Dicts/GraphAn/abbrev.log -------------------------------------------------------------------------------- /dicts/src/Dicts/GraphAn/enames.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmike/pymorphy/9a6614449447c6ceee35e6d0dfe36903ebc80c5f/dicts/src/Dicts/GraphAn/enames.txt -------------------------------------------------------------------------------- /dicts/src/Dicts/GraphAn/extensions.txt: -------------------------------------------------------------------------------- 1 | arc 2 | arj 3 | bat 4 | bin 5 | bmp 6 | cmd 7 | com 8 | dbf 9 | dll 10 | doc 11 | dvi 12 | exe 13 | gz 14 | Hqx 15 | htm 16 | html 17 | lhz 18 | mrw 19 | nsf 20 | pdb 21 | ps 22 | rcf 23 | rtf 24 | shar 25 | sit 26 | slf 27 | syn 28 | sys 29 | tar 30 | tex 31 | txt 32 | wav 33 | win 34 | z 35 | zip 36 | zoo 37 | -------------------------------------------------------------------------------- /dicts/src/Dicts/GraphAn/idents.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmike/pymorphy/9a6614449447c6ceee35e6d0dfe36903ebc80c5f/dicts/src/Dicts/GraphAn/idents.txt -------------------------------------------------------------------------------- /dicts/src/Dicts/GraphAn/keyboard.txt: -------------------------------------------------------------------------------- 1 | [modifiers] 2 | shift 3 | ctrl 4 | alt 5 | control 6 | Shift 7 | Ctrl 8 | Alt 9 | Control 10 | SHIFT 11 | CTRL 12 | ALT 13 | CONTROL 14 | 15 | [keys] 16 | PgUp 17 | F1 18 | F2 19 | @ 20 | Page Down 21 | ScrLock 22 | Scroll Lock 23 | Caps Lock 24 | CapsLock 25 | Num Lock 26 | NumLock 27 | Del 28 | Delete 29 | 30 | -------------------------------------------------------------------------------- /dicts/src/Dicts/GraphAn/keyword: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmike/pymorphy/9a6614449447c6ceee35e6d0dfe36903ebc80c5f/dicts/src/Dicts/GraphAn/keyword -------------------------------------------------------------------------------- /dicts/src/Dicts/GraphAn/ross.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmike/pymorphy/9a6614449447c6ceee35e6d0dfe36903ebc80c5f/dicts/src/Dicts/GraphAn/ross.txt -------------------------------------------------------------------------------- /dicts/src/Dicts/GraphAn/space.dic: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmike/pymorphy/9a6614449447c6ceee35e6d0dfe36903ebc80c5f/dicts/src/Dicts/GraphAn/space.dic -------------------------------------------------------------------------------- /dicts/src/Dicts/Morph/Eng/morph.options: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmike/pymorphy/9a6614449447c6ceee35e6d0dfe36903ebc80c5f/dicts/src/Dicts/Morph/Eng/morph.options -------------------------------------------------------------------------------- /dicts/src/Dicts/Morph/Rus/morph.options: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmike/pymorphy/9a6614449447c6ceee35e6d0dfe36903ebc80c5f/dicts/src/Dicts/Morph/Rus/morph.options -------------------------------------------------------------------------------- /dicts/src/Dicts/Morph/egramtab.tab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmike/pymorphy/9a6614449447c6ceee35e6d0dfe36903ebc80c5f/dicts/src/Dicts/Morph/egramtab.tab -------------------------------------------------------------------------------- /dicts/src/Dicts/Morph/rgramtab.tab: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmike/pymorphy/9a6614449447c6ceee35e6d0dfe36903ebc80c5f/dicts/src/Dicts/Morph/rgramtab.tab -------------------------------------------------------------------------------- /dicts/src/Dicts/SrcMorph/Eng.mwz: -------------------------------------------------------------------------------- 1 | MRD_FILE EngSrc/morphs.mrd 2 | LANG ENGLISH 3 | USERS gri,alex,boris,masha,af,oleg,nim 4 | -------------------------------------------------------------------------------- /dicts/src/Dicts/SrcMorph/EngSrc/morphs.mrd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmike/pymorphy/9a6614449447c6ceee35e6d0dfe36903ebc80c5f/dicts/src/Dicts/SrcMorph/EngSrc/morphs.mrd -------------------------------------------------------------------------------- /dicts/src/Dicts/SrcMorph/Rus.mwz: -------------------------------------------------------------------------------- 1 | MRD_FILE RusSrc/morphs.mrd 2 | LANG RUSSIAN 3 | USERS alex,vse-imena,accentor,user2008 -------------------------------------------------------------------------------- /dicts/src/Dicts/SrcMorph/RusSrc/morphs.mrd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmike/pymorphy/9a6614449447c6ceee35e6d0dfe36903ebc80c5f/dicts/src/Dicts/SrcMorph/RusSrc/morphs.mrd -------------------------------------------------------------------------------- /dicts/src/Docs/Morph_UNIX.txt: -------------------------------------------------------------------------------- 1 | This is a program of morphological analysis (Russian, German, and English languages). 2 | 3 | This program is distributed under the Library GNU Public Licence, which is in the file 4 | COPYING. 5 | 6 | This program was written by Andrey Putrin, Alexey Sokirko. 7 | The project started in Moscow in Dialing 8 | Company (Russian and English language). The German part was created 9 | at Berlin-Brandenburg Academy of Sciences and Humanities in Berlin (the project DWDS). 10 | 11 | The Russian lexicon is based upon Zaliznyak's Dictionary . 12 | The German lexicon is based upon Morphy system (http://www-psycho.uni-paderborn.de/lezius/). 13 | The English lexicon is based upon Wordnet. 14 | 15 | The project uses a regular expression library "PCRE" (Perl Compatible Regular Expressions). 16 | We test compilation only with version 6.4. Other versions were not tested. 17 | One should download this version from the official site and install it 18 | to the default place. If you do not want to install it or you do not have enough 19 | rights to do it, then you should create two environment variables: 20 | 1. RML_PCRE_LIB, that points to PCRE library directory, where 21 | libpcre.a and libpcrecpp.a should be located, for example: 22 | export RML_PCRE_LIB=~/RML/contrib/pcre-6.4/.libs 23 | 2 RML_PCRE_INCLUDE, that points to PCRE include catalog, 24 | where "pcrecpp.h" is located, for example 25 | export RML_PCRE_INCLUDE=~/RML/contrib/pcre-6.4 26 | 27 | 28 | The system has been developed under Windows 2000 (MS VS 6.0), but 29 | has also been compiled and run under Linux(GCC). It should work with 30 | minor changes on other systems. 31 | 32 | Website of DDC: www.aot.ru, https://sf.net/projects/morph-lexicon/ 33 | 34 | I compiled all sources with gcc 3.2. Lower versions are not supported. 35 | 36 | 37 | Contents of the this source archive 38 | 39 | 1. The main morphological library (Source/LemmatizerLib). 40 | 2. Library for grammatical codes (Source/AgrgamtabLib). 41 | 3. Test morphological program (Source/TestLem).. 42 | 4. Library for working with text version of the dictionaries (Source/MorphWizardLib). 43 | 5. Generator of morphological prediction base (Source/GenPredIdx). 44 | 6. Generator of binary format of the dictionaries (Source/MorphGen). 45 | 46 | 47 | ================================================= 48 | ====== Installation ===== 49 | ================================================= 50 | 51 | 52 | Unpacking 53 | 54 | * Create a catalog and register a system variable RML, which points 55 | to this catalog: 56 | mkdir /home/sokirko/RML 57 | export RML=/home/sokirko/RML 58 | 59 | * Put "lemmatizer.tar.gz", "???-src-morph.tar.gz" 60 | to this catalog, "???" can be "rus", "ger" or "eng" 61 | according to what you have downloaded. Unpack it 62 | tar xfz lemmatizer.tar.gz 63 | tar xfz ???-src-morph.tar.gz 64 | 65 | 66 | 67 | Compiling morphology 68 | 69 | 0. Do not forget to set RML_PCRE (see above) 70 | 71 | 72 | 1. cd $RML 73 | 74 | 75 | 2. ./compile_morph.sh 76 | This step should create all libraries and a test program $RML\Bin\TestLem. 77 | 78 | 79 | Building Morphological Dictionary 80 | 81 | 1. cd $RML 82 | 83 | 2. ./generate_morph_bin.sh 84 | where can be Russian, German according to the dictionary 85 | yo have downloaded. 86 | 87 | The script should terminate with message "Everything is OK". 88 | You can test the morphology 89 | $RML\Bin\TestLem 90 | 91 | 92 | 93 | 94 | 95 | If something goes wrong, write me to sokirko@yandex.ru. 96 | 97 | 98 | 99 | 100 | ====================================================== 101 | ========== MRD-file ============ 102 | ====================================================== 103 | 104 | This section describes the format of a mrd-file. Mrd-file is a text 105 | file which contains one morphological dictionary for one natural language. 106 | MRD is an abbreviation of "morphological dictionary". 107 | The usual place for this file is 108 | 109 | $RML/Dicts/SrcMorph/xxxSrc/morphs.mrd, 110 | 111 | where xxx can be "Eng", "Rus" or "Ger" depending on the language. 112 | The encoding of the file depends also upon the language: 113 | * Russian - Windows 1251 114 | * German - Windows 1252 115 | * English - ASCII 116 | 117 | 118 | Gramtab-files 119 | 120 | 121 | A mrd-file refers to a gramtab-file, which is 122 | language-dependent and which contains all possible full morphological 123 | patterns for the words. One line in a gramtab-file looks like as follows: 124 | 125 | An ancode is an ID, which consists of two letters and which uniquely 126 | identifies a morphological pattern. A morphological pattern consists of 127 | and . For example, here is a line from the English 128 | gramtab: 129 | 130 | te 1 VBE prsa,pl 131 | 132 | Here "te" is an ancode, "VBE" is a part of speech, "prsa,pl" are grammems, 133 | "1" is the obsolete unused number. 134 | In mrd-files we use ancodes to refer to a morphological pattern. 135 | 136 | Here is the list of all gramtab-files: 137 | * Russian - $Rml/Dicts/Morph/rgramtab.tab 138 | * German - $Rml/Dicts/Morph/ggramtab.tab 139 | * English - $Rml/Dicts/Morph/egramtab.tab 140 | 141 | 142 | 143 | Common information 144 | 145 | 146 | All words in a mrd-file are written in uppercase. 147 | One mrd-file consists of the following sections: 148 | 1. Section of flexion and prefix models; 149 | 2. Section of accentual models; 150 | 3. Section of user sessions; 151 | 4. Section of prefix sets; 152 | 5. Section of lemmas. 153 | Each section is a set of records, one per line. The number of all records 154 | of the section is written in the very beginning of the section at 155 | a separate line. For example, here is a possible variant 156 | of the section of user sessions: 157 | 158 | 1 159 | alex;17:10, 13 October 2003;17:12, 13 October 2003 160 | 161 | "1" means that this section contains only one record, which is written 162 | on the next line, thus this section contains only two lines. 163 | 164 | 165 | 166 | Section of possible flexion and prefix models 167 | 168 | 169 | Each record of this section is a list of items. Each item 170 | describes how one word form in a paradigm should be built. The whole list 171 | describes the whole paradigm (a set of word forms with morphological patterns). 172 | The format of one item is the following: 173 | %* 174 | or %** 175 | where 176 | is a flexion (a string, which should be added to right of the word base) 177 | is a prefix (a string, which should be added to left of the word base) 178 | is an ancode. 179 | Let us consider an example of an English flexion and prefix model: 180 | %F*na%VES*nb 181 | Here we have two items: 182 | 1. = F; = na 183 | 2. = VES; = nb 184 | In order to decipher ancodes we should go the English gramtab-file. 185 | There we can find the following lines: 186 | na NOUN narr,sg 187 | nb NOUN narr,pl 188 | If base "lea" would be ascribed to this model, then its paradigm 189 | would be the following: 190 | leaf NOUN narr,sg 191 | leaves NOUN narr,pl 192 | It is important, that each word of a morphological dictionary 193 | should contain a reference to a line in this section. 194 | 195 | 196 | Section of possible accentual models 197 | 198 | 199 | Each record of this section is a comma-delimited list of numbers, where 200 | each number is an index of a stressed vowel of a word form(counting 201 | from the end). The whole list contains a position for each word 202 | form in the paradigm. 203 | If an item of an accentual model of word is equal to 255, then it 204 | is undefined, and it means that this word form is unstressed. 205 | Each word in the dictionary should have a reference to 206 | an accentual model, even though this model can consist only of empty items. 207 | For one word, the number and the order of items in the accentual model 208 | should be equal to the number and the order of items in the flexion and 209 | prefix model. For example we can ascribe to word "leaf" with the paradigm 210 | leaf NOUN narr,sg 211 | leaves NOUN narr,pl 212 | the following accentual model: 213 | 214 | 2,3 215 | 216 | It produces the following accented paradigm: 217 | le'af NOUN narr,sg 218 | le'aves NOUN narr,pl 219 | 220 | 221 | 222 | Section of user section 223 | 224 | This is a system section, which contains information about user edit 225 | sessions. 226 | 227 | 228 | Section of prefix sets 229 | 230 | Each record of this section is a comma-delimited list of strings, where 231 | each string is a prefix, which can be prefixed to the whole word. If a prefix 232 | set is ascribed to a word, it means, that the words with these prefixes 233 | can also exist in the language. For example, if "leaf" has 234 | the prefix set "anti,contra", it follows the existence of words "antileaf", 235 | "contraleaf". 236 | A flexion and prefix model can contain 237 | also a reference to a prefix, but this prefix is for 238 | one separate word form, while a prefix set is ascribed to the whole word 239 | paradigm. 240 | 241 | 242 | Section of lemmas 243 | 244 | A record of this section is a space-separated tuple of the following format: 245 | 246 | 247 | 248 | where 249 | 250 | is a base (a constant part of a word in its paradigm) 251 | is an index of a flexion and prefix model 252 | is an index of an accentual model 253 | is an index of the session, by which the last user edited this word 254 | is ancode, which is ascribed to the whole word 255 | (intended: the common part of grammems in the paradigm) 256 | "-" if it is undefined 257 | is an index of a prefix set, or "-" if it is undefined 258 | 259 | -------------------------------------------------------------------------------- /docs/CHANGES.rst: -------------------------------------------------------------------------------- 1 | 2 | История изменений 3 | ================= 4 | 5 | (dev) 6 | ----- 7 | 8 | Этот релиз обратно несовместим с 0.5.6: сменилась структура словарей, 9 | убрана поддержка python 2.5. Для обновления скачайте новые словари. 10 | TODO: выложить словари. 11 | 12 | * Объявлены устаревшими бэкенды, использующие shelve (не быстр, не везде 13 | работает, формат словарей не переносим), pytc (сложно устанавливать, 14 | нет преимуществ). Вместо этого добавлена поддержка словарей cdb через 15 | библиотеки tinycdb (она MIT/Public Domain, по скорости на уровне pytc, 16 | потребляет памяти меньше остальных) и cdblib. 17 | FIXME: tinycdb не на pypi. 18 | * улучшения в разборщике фамилий (API ближе к основному, больше тестов); 19 | * слегка упрощен скрипт перекодирования словарей; 20 | * экспериментальная поддержка python 3.2 и pypy 1.7+; python 2.5 больше не 21 | поддерживается; 22 | * исправлена работа sqlite-бекенда в многопоточном окружении (еще раз); 23 | * тесты запускаются для всех поддерживаемых интерпретаторов через tox; 24 | * сменился формат словаря - увеличение скорости в несколько раз ценой 25 | некоторого увеличения потребления оперативной памяти; 26 | * рефакторинг класса Morph - за базовые функции склонения теперь отвечает 27 | источник данных, а не морф. анализатор, что теоретически должно позволить 28 | реализовывать более быстрые-эффективные по памяти варианты разбора с 29 | использованием более подходящих структур данных; 30 | * значения по умолчанию для функции get_morph могут браться из переменных 31 | окружения PYMORPHY_DICTIONARY_PATH и PYMORPHY_DICTIONARY_BACKEND; 32 | * модуль ``pymorphy.contrib.word_case`` для восстановления строчных-заглавных 33 | букв после обработки. 34 | 35 | 36 | 0.5.6 (2011-09-11) 37 | ------------------ 38 | 39 | * в релиз 0.5.5 по ошибке не вошли файлы разборщика фамилий и вошли 40 | некоторые ненужные файлы - это исправлено. 41 | * улучшения в разборе фамилий; 42 | * решена проблема со склонением слова "КИЕВ" и других, которые склонятор 43 | раньше пытался склонять как существительные в косвенных падежах; 44 | 45 | 0.5.5 (2011-08-13) 46 | ------------------ 47 | 48 | * улучшена и упрощена документация; 49 | * pymorphy-speedups пересобран с Cython 0.15 (для cdb и sqlite с cache=True 50 | это почему-то дает ускорение до 1.5..2x); 51 | * исправлена ошибка в склонении слов (иногда при склонении бралась неосновная 52 | форма слова); 53 | * исправлена совместная работа pymorphy-speedups и шаблонных фильтров django: 54 | фильтры не работали для "умных" строк (например, SafeString или 55 | ugettext_lazy-строк); 56 | * очень экспериментальная поддержка склонения фамилий (отдельно от основного 57 | анализатора; API будет изменен в последующих релизах). 58 | 59 | 0.5.4 (2011-07-15) 60 | ------------------ 61 | 62 | * Убрано предупреждение при одновременном обновлении pymorphy и 63 | pymorphy-speedups (например, через файл с зависимостями pip); 64 | * файлы setup.py и скрипт запуска тестов теперь всегда используют 65 | "родной" pymorphy, а не установленный в систему. 66 | 67 | 0.5.3 (2011-07-15) 68 | ------------------ 69 | 70 | * Исправлена ошибка с определением версии pymorphy-speedups; 71 | * вместо ``pymorphy.split`` теперь ``pymorphy.contrib.tokenizers`` 72 | с функциями ``extract_tokens`` и ``extract_words``; 73 | * поправлена установка из hg-репозитория для windows. 74 | 75 | 0.5.2 (2011-04-09) 76 | ------------------ 77 | * Исправлены ошибки в sqlite-словарях (**внимание**: 78 | `скачайте новые словари `_ 79 | для обновления); 80 | * представление данных в json теперь компактнее, поэтому при использовании 81 | новых словарей должна повыситься скорость работы (особенно при отключенном 82 | кешировании); 83 | * ускорение отключается с предупрежденем, если версия pymorphy-speedups 84 | не соответствует версии pymorphy; 85 | * исправлены опечатки в документации; 86 | * в тесты включен скрипт разбора "Золотого стандарта" с ДИАЛОГ-2010; 87 | * в скрипт для конвертации словарей добавлена перекрестная проверка их 88 | корректности. 89 | 90 | 0.5.1 (2011-02-10) 91 | ------------------ 92 | * Sqlite-бэкенд теперь должен работать в многопоточном окружении; 93 | * исправлена ошибка с последовательным применением шаблонных фильтров 94 | для django. 95 | 96 | 0.5.0 (2010-11-15) 97 | ------------------ 98 | * исправления и дополнения в документации 99 | * для тестов используется unittest2 100 | * поддержка опционального модуля 101 | `pymorphy-speedups `_ с 102 | расширением на Cython (туда также перенесен метод setup_psyco). При 103 | использовании pickle-словарей скорость при установке расширения должна 104 | увеличиться в 2+ раза. Для других (более медленных) вариантов словарей 105 | относительный прирост будет не таким значительным. Осторожно: при установленном 106 | модуле все строки должны передаваться как юникодные (в.т.ч. латинские и 107 | пустые). 108 | * убрана зависимость от simplejson (но его лучше все равно поставить, т.к. с 109 | simplejson работа со всеми словарями, кроме pickle, ускоряется в несколько раз) 110 | * правильное склонение слов во втором предложном, родительном или винительном 111 | падежах 112 | * метод pluralize_inflected_ru теперь поддерживает не только существительные 113 | * более правильное разбиение на слова в фильтрах 114 | * Работа со словами, записанными через дефис. 115 | * Поддержка парсинга распознанных текстов (характерные замены букв). Довольно 116 | бесполезная штука. 117 | * Убран метод get_normal_forms, т.к. метод get_gram_info и так возвращает 118 | для каждого слова нормальную форму. 119 | 120 | 0.4.3 (2010-02-06) 121 | ------------------ 122 | Устранены небольшие ошибки. 123 | 124 | 0.4.0 (2010-01-07) 125 | ------------------ 126 | Упрощена установка: добавилась поддержка кроссплатформенных словарей в sqlite 127 | 128 | 0.3.5 (2009-12-15) 129 | ------------------ 130 | Интеграция с django: добавлены шаблонные фильтры для склонения и согласования 131 | слов. Переделаны правила получения нормальных форм слова (переделка ошибочная). 132 | 133 | 0.1.0 (2009-12-07) 134 | ------------------ 135 | pymorphy почти полностью переписан, документирован, оформлен как 136 | python-пакет и загружен на pypi.python.org 137 | 138 | 0.0.1 (2009-01-18) 139 | ------------------ 140 | первая версия, которая после написания была заброшена на год 141 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | 15 | .PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest 16 | 17 | help: 18 | @echo "Please use \`make ' where is one of" 19 | @echo " html to make standalone HTML files" 20 | @echo " dirhtml to make HTML files named index.html in directories" 21 | @echo " pickle to make pickle files" 22 | @echo " json to make JSON files" 23 | @echo " htmlhelp to make HTML files and a HTML help project" 24 | @echo " qthelp to make HTML files and a qthelp project" 25 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 26 | @echo " changes to make an overview of all changed/added/deprecated items" 27 | @echo " linkcheck to check all external links for integrity" 28 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 29 | 30 | clean: 31 | -rm -rf $(BUILDDIR)/* 32 | 33 | html: 34 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 35 | @echo 36 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 37 | 38 | dirhtml: 39 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 40 | @echo 41 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 42 | 43 | pickle: 44 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 45 | @echo 46 | @echo "Build finished; now you can process the pickle files." 47 | 48 | json: 49 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 50 | @echo 51 | @echo "Build finished; now you can process the JSON files." 52 | 53 | htmlhelp: 54 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 55 | @echo 56 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 57 | ".hhp project file in $(BUILDDIR)/htmlhelp." 58 | 59 | qthelp: 60 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 61 | @echo 62 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 63 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 64 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pymorphy.qhcp" 65 | @echo "To view the help file:" 66 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pymorphy.qhc" 67 | 68 | latex: 69 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 70 | @echo 71 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 72 | @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ 73 | "run these through (pdf)latex." 74 | 75 | changes: 76 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 77 | @echo 78 | @echo "The overview file is in $(BUILDDIR)/changes." 79 | 80 | linkcheck: 81 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 82 | @echo 83 | @echo "Link check complete; look for any errors in the above output " \ 84 | "or in $(BUILDDIR)/linkcheck/output.txt." 85 | 86 | doctest: 87 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 88 | @echo "Testing of doctests in the sources finished, look at the " \ 89 | "results in $(BUILDDIR)/doctest/output.txt." 90 | 91 | ziphtml: 92 | cd $(BUILDDIR)/html/; zip -r pymorphy.zip ./* 93 | -------------------------------------------------------------------------------- /docs/algo.rst: -------------------------------------------------------------------------------- 1 | Как работает pymorphy 2 | ##################### 3 | 4 | Общая информация 5 | ================ 6 | 7 | pymorphy - библиотека для морфологического анализа на Python, 8 | распространяется по лицензии MIT. 9 | 10 | За основу были взяты наработки с сайта aot.ru. 11 | Словари (LGPL) для русского и английского, а также идеи - оттуда. 12 | 13 | На aot.ru описаны и конкретные алгоритмы реализации, но в терминах 14 | теории автоматов. Реализация в pymorphy независимая, не использует 15 | конечные автоматы, данные хранятся в key-value хранилище (поддерживаются разные 16 | варианты), все алгоритмы переписаны с учетом этого факта. 17 | 18 | В pymorphy также есть некоторые возможности, отсутствующие 19 | в оригинальной реализации, например: 20 | 21 | * поддерживается разбор слов с дефисами, разбор слов со сложными префиксами; 22 | * реализовано склонение слов, постановка слов во множественное число; 23 | 24 | Cловари aot.ru 25 | ============== 26 | 27 | Словари с сайта aot.ru содержат следующую информацию: 28 | 29 | 1. парадигмы слов и конкретные правила образования; 30 | 2. ударения; 31 | 3. пользовательские сессии; 32 | 4. набор префиксов (продуктивных приставок); 33 | 5. леммы (незменяемые части слова, основы); 34 | 6. грамматическая информация - в отдельном файле. 35 | 36 | .. note:: 37 | 38 | См. также :ref:`описание формата mrd-файла ` 39 | 40 | Из этого всего нам интересны правила образования слов, префиксы, леммы и 41 | грамматическая информация. 42 | 43 | Все слова образуются по одному принципу:: 44 | 45 | префикс + приставка + основа + окончание 46 | 47 | .. glossary:: 48 | 49 | Префиксы 50 | Префиксы - это всякие "мега", "супер" и т.д. Набор префиксов 51 | хранится просто списком. 52 | 53 | Приставки 54 | Имеются в виду приставки, присущие грамматической форме, 55 | но не присущие неизменяемой части слова ("по", "наи"). Например, 56 | "наи" в слове "наикрасивейший", т.к. без превосходной степени 57 | будет "красивый". 58 | 59 | Правила образования слов 60 | Это то, что надо приписать спереди и сзади основы, чтобы получить 61 | какую-то форму. В словаре хранятся пары "приставка - окончание", 62 | + "номер" записи о грамматической информации (которая хранится 63 | отдельно). 64 | 65 | Парадигмы 66 | Правила образования слов объединены в *парадигмы*. Например, для 67 | какого-нибудь класса существительных может быть описано, как слово 68 | выглядит во всех падежах и родах. Зная, что существительное 69 | принадлежит к этому классу, мы сможем правильно получить любую его 70 | форму. Такой класс - это и есть парадигма. 71 | 72 | Леммы 73 | Леммы - это неизменяемые части слов. В словаре хранится 74 | информация о том, какой лемме соответствуют какие парадигмы 75 | (какой набор правил для образования грамматических форм слова). 76 | Одной лемме может соответствовать несколько парадигм. 77 | 78 | Грамматическая информация 79 | Грамматическая информация - просто пары ("номер" записи, грам. информация). 80 | "Номер" в кавычках, т.к. это 2 буквы, просто от балды, но все разные. 81 | 82 | .. note:: 83 | 84 | Неправильно считать, что :term:`лемма <Леммы>` - это корень слова, 85 | или :term:`приставки <Приставки>` означают то же самое, что и на уроке 86 | русского языка. Привычные со школы категории (корень, суффикс, приставка, 87 | окончание) в pymorphy не используются или имеют другой смысл, т.к. эти 88 | категории слишком слабо формализованы и поэтому не очень подходят 89 | для машинного морфологического анализа. 90 | 91 | Файл со словарем - обычный текстовый, для каждого раздела сначала указано 92 | число строк в нем, а потом идут строки, формат их описан тут. 93 | 94 | Поняв структуру словаря, можно написать первую версию морфологического анализатора. 95 | 96 | Морфологический анализ 97 | ====================== 98 | 99 | По сути, нам дано слово, и его надо найти среди всех разумных комбинаций вида:: 100 | 101 | префикс + приставка + лемма + окончание 102 | 103 | и:: 104 | 105 | приставка + лемма + окончание 106 | 107 | Дело упрощает то, что оказалось (как показала пара строчек на питоне), 108 | что "приставок" у нас в языке (да и в английском вроде тоже) всего 2. 109 | А префиксов в словаре - порядка 20 для русского языка. Поэтому искать 110 | можно среди комбинаций:: 111 | 112 | префикc + лемма + окончание 113 | 114 | объединив в уме список приставок и префиксов, а затем выполнив 115 | небольшую проверочку. 116 | 117 | Если слово начинается с одного из возможных префиксов, 118 | то мы его (префикс) отбрасываем и пытаемся морфологически 119 | анализировать остаток (рекурсивно), а потом просто припишем 120 | отброшенный префикс к полученным формам. 121 | 122 | В итоге получается, что задача сводится к поиску среди комбинаций:: 123 | 124 | лемма + окончание 125 | 126 | Ищем подходящие леммы, потом смотрим, есть ли для них подходящие окончания. [#]_ 127 | 128 | Для поиска задействован стандартный питоновский ассоциативный массив 129 | (dict, или любой объект, поддерживающий ``__getitem__``, 130 | ``__setitem__`` и ``__contains__``), в который поместил все леммы. 131 | Получился словарь вида:: 132 | 133 | lemmas: {base -> [paradigm_id]} 134 | 135 | т.е. ключ - это лемма, а значение - список номеров допустимых парадигм. 136 | А дальше поехали - сначала считаем, что лемма - это первая буква слова, 137 | потом, что это 2 первых буквы и т.д. По лемме пытаемся получить список 138 | парадигм. Если получили, то в каждой допустимой парадигме пробегаем по 139 | всем правилам и смотрим, получится ли наше слово, если правило применить. 140 | Получается - добавляем его в список найденных форм. 141 | 142 | .. rubric:: Примечания 143 | 144 | .. [#] Еще был вариант - составить сразу словарь всех возможных слов 145 | вида ``лемма + окончание``, получалось в итоге где-то миллионов 5 146 | слов, не так и много, но вариант, вообщем, мне не очень понравился. 147 | 148 | 149 | Дополнительные детали работы морфологического анализатора 150 | --------------------------------------------------------- 151 | 152 | Слова без неизменяемой части 153 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 154 | 155 | Если вспомнить пример, который был в начале, про "ЛЮДЕЙ" - "ЧЕЛОВЕК", то 156 | станет понятно, что есть слова, у которых неизменяемая часть отсутствует. 157 | Выяснилось, что есть в словаре такая хитрая магическая лемма "#", которая и 158 | соответствует всем пустым леммам. Для всех слов нужно искать еще и там. 159 | 160 | Склонение слов 161 | ^^^^^^^^^^^^^^ 162 | 163 | Для "склонения" слова (постановке его в определенную грамматическую форму) 164 | анализатор сначала составляет список всех форм, в которых может находиться 165 | данное слово, потом убирает из них те, которые не соответствуют переданной 166 | форме, а потом выбирает из оставшихся вариант, по форме наиболее близкий к 167 | исходному. 168 | 169 | Постановка слов во множественное число после этого тривиальным образом 170 | реализуется через "склонение". 171 | 172 | .. _prediction-algo: 173 | 174 | Предсказатель 175 | ------------- 176 | 177 | Реализован "предсказатель", который может работать со словами, 178 | которых нет в словаре. Это не только неизвестные науке редкие слова, 179 | но и просто описки, например. 180 | 181 | Для предсказателя реализованы 2 подхода, которые работают совместно. 182 | 183 | Первый подход: угадывание префикса 184 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 185 | 186 | Если слова отличаются только тем, что к одному из них приписано 187 | что-то спереди, то, скорее всего, склоняться они будут однаково. 188 | 189 | Реализуется очень просто: пробуем считать сначала одну первую букву 190 | слова префиксом, потом 2 первых буквы и т.д. А то, что осталось, 191 | передаем морфологическому анализатору. Ну и делаем это только для не очень 192 | длинных префиксов и не очень коротких остатков. 193 | 194 | Второй подход: предсказание по концу слова 195 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 196 | 197 | Если 2 слова оканчиваются одинаково, то и склоняться они, скорее всего, 198 | будут одинаково. 199 | 200 | Второй подход чуть сложнее в реализации (так-то сильно сложнее, если нужна 201 | хорошая реализация)) и "поумнее" в плане предсказаний. 202 | 203 | Первая сложность связана с тем, что конец слова может состоять не только из 204 | окончания, но и из части леммы. Для простоты тут задействован опять 205 | ассоциативный массив (или duck typing-заменитель) с предварительно 206 | подготовленными всеми возмоными окончаниями слов (до 5 букв). 207 | Их получилось несколько сот тысяч. Ключ массива - конец слова, значение - 208 | список возможных правил. Дальше - все как при поиске подходящей леммы, 209 | только у слова берем не начало, а 1, 2, 3, 4, 5-буквенные концы, а вместо лемм 210 | у нас теперь новый монстромассив. 211 | 212 | Вторая сложность - получается много заведомого мусора. Мусор этот отсекается, 213 | если учесть, что полученные слова могут быть только существительными, 214 | прилагательными, наречиями или глаголами. 215 | 216 | Даже после этого у нас остается слишком много не-мусорных правил. 217 | Для определенности, для каждой части речи оставляем только самое 218 | распространенное правило. 219 | 220 | .. note:: 221 | 222 | Если слово не было предсказано как существительное, 223 | хорошо бы добавить вариант с неизменяемым существительным 224 | в ед.ч. и.п., но этот участок кода сейчас закомментирован, 225 | т.к. на практике он не давал почти никакого улучшения качества 226 | разбора при большом числе ложных срабатываний. 227 | 228 | Сложные слова 229 | ------------- 230 | 231 | В версии 0.5 появилась поддержка разбора сложных слов, записанных через дефис 232 | (например, "ПО-БРАТСКИ" или "ЧЕЛОВЕК-ПАУК"). 233 | 234 | Поддерживаются слова, образованные 2 способами: 235 | 236 | * левая часть - неизменяемая приставка/основа (например, "ИНТЕРНЕТ-МАГАЗИН", 237 | "ВОЗДУШНО-КАПЕЛЬНЫЙ". В этом случае форма слова определяется второй частью. 238 | Этот случай добавляется в возможные варианты разбора всегда. 239 | * 2 равноправные части, склоняемые вместе (например, "ЧЕЛОВЕК-ПАУК"). Этот 240 | случай добавляется в возможные варианты разбора только тогда, когда обе части 241 | имеют одинаковую форму (есть варианты разбора первой части, которые 242 | совпадают с вариантами разбора второй). 243 | 244 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # pymorphy documentation build configuration file, created by 4 | # sphinx-quickstart on Sun Dec 6 14:14:52 2009. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | sys.path.insert(0, os.path.abspath(os.path.join(os.path.curdir,'..'))) 16 | 17 | 18 | # If extensions (or modules to document with autodoc) are in another directory, 19 | # add these directories to sys.path here. If the directory is relative to the 20 | # documentation root, use os.path.abspath to make it absolute, like shown here. 21 | #sys.path.append(os.path.abspath('.')) 22 | 23 | # -- General configuration ----------------------------------------------------- 24 | 25 | # Add any Sphinx extension module names here, as strings. They can be extensions 26 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 27 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.coverage'] 28 | 29 | # Add any paths that contain templates here, relative to this directory. 30 | templates_path = ['_templates'] 31 | 32 | # The suffix of source filenames. 33 | source_suffix = '.rst' 34 | 35 | # The encoding of source files. 36 | #source_encoding = 'utf-8' 37 | 38 | # The master toctree document. 39 | master_doc = 'index' 40 | 41 | # General information about the project. 42 | project = u'pymorphy' 43 | copyright = u'2009-2011, Mikhail Korobov and contributors' 44 | 45 | # The version info for the project you're documenting, acts as replacement for 46 | # |version| and |release|, also used in various other places throughout the 47 | # built documents. 48 | # 49 | # The short X.Y version. 50 | version = '0.5' 51 | # The full version, including alpha/beta/rc tags. 52 | release = '0.5.6' 53 | 54 | # The language for content autogenerated by Sphinx. Refer to documentation 55 | # for a list of supported languages. 56 | language = 'ru' 57 | 58 | # There are two options for replacing |today|: either, you set today to some 59 | # non-false value, then it is used: 60 | #today = '' 61 | # Else, today_fmt is used as the format for a strftime call. 62 | #today_fmt = '%B %d, %Y' 63 | 64 | # List of documents that shouldn't be included in the build. 65 | #unused_docs = [] 66 | 67 | # List of directories, relative to source directory, that shouldn't be searched 68 | # for source files. 69 | exclude_trees = ['_build'] 70 | 71 | # The reST default role (used for this markup: `text`) to use for all documents. 72 | #default_role = None 73 | 74 | # If true, '()' will be appended to :func: etc. cross-reference text. 75 | #add_function_parentheses = True 76 | 77 | # If true, the current module name will be prepended to all description 78 | # unit titles (such as .. function::). 79 | #add_module_names = True 80 | 81 | # If true, sectionauthor and moduleauthor directives will be shown in the 82 | # output. They are ignored by default. 83 | #show_authors = False 84 | 85 | # The name of the Pygments (syntax highlighting) style to use. 86 | pygments_style = 'sphinx' 87 | 88 | # A list of ignored prefixes for module index sorting. 89 | #modindex_common_prefix = [] 90 | 91 | 92 | # -- Options for HTML output --------------------------------------------------- 93 | 94 | # The theme to use for HTML and HTML Help pages. Major themes that come with 95 | # Sphinx are currently 'default' and 'sphinxdoc'. 96 | html_theme = 'default' 97 | 98 | # Theme options are theme-specific and customize the look and feel of a theme 99 | # further. For a list of options available for each theme, see the 100 | # documentation. 101 | #html_theme_options = {} 102 | 103 | # Add any paths that contain custom themes here, relative to this directory. 104 | #html_theme_path = [] 105 | 106 | # The name for this set of Sphinx documents. If None, it defaults to 107 | # " v documentation". 108 | #html_title = None 109 | 110 | # A shorter title for the navigation bar. Default is the same as html_title. 111 | #html_short_title = None 112 | 113 | # The name of an image file (relative to this directory) to place at the top 114 | # of the sidebar. 115 | #html_logo = None 116 | 117 | # The name of an image file (within the static path) to use as favicon of the 118 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 119 | # pixels large. 120 | #html_favicon = None 121 | 122 | # Add any paths that contain custom static files (such as style sheets) here, 123 | # relative to this directory. They are copied after the builtin static files, 124 | # so a file named "default.css" will overwrite the builtin "default.css". 125 | html_static_path = ['_static'] 126 | 127 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 128 | # using the given strftime format. 129 | #html_last_updated_fmt = '%b %d, %Y' 130 | 131 | # If true, SmartyPants will be used to convert quotes and dashes to 132 | # typographically correct entities. 133 | #html_use_smartypants = True 134 | 135 | # Custom sidebar templates, maps document names to template names. 136 | #html_sidebars = {} 137 | 138 | # Additional templates that should be rendered to pages, maps page names to 139 | # template names. 140 | #html_additional_pages = {} 141 | 142 | # If false, no module index is generated. 143 | #html_use_modindex = True 144 | 145 | # If false, no index is generated. 146 | #html_use_index = True 147 | 148 | # If true, the index is split into individual pages for each letter. 149 | #html_split_index = False 150 | 151 | # If true, links to the reST sources are added to the pages. 152 | #html_show_sourcelink = True 153 | 154 | # If true, an OpenSearch description file will be output, and all pages will 155 | # contain a tag referring to it. The value of this option must be the 156 | # base URL from which the finished HTML is served. 157 | #html_use_opensearch = '' 158 | 159 | # If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). 160 | #html_file_suffix = '' 161 | 162 | # Output file base name for HTML help builder. 163 | htmlhelp_basename = 'pymorphydoc' 164 | 165 | 166 | # -- Options for LaTeX output -------------------------------------------------- 167 | 168 | # The paper size ('letter' or 'a4'). 169 | #latex_paper_size = 'letter' 170 | 171 | # The font size ('10pt', '11pt' or '12pt'). 172 | #latex_font_size = '10pt' 173 | 174 | # Grouping the document tree into LaTeX files. List of tuples 175 | # (source start file, target name, title, author, documentclass [howto/manual]). 176 | latex_documents = [ 177 | ('index', 'pymorphy.tex', u'pymorphy Documentation', 178 | u'Mikhail Korobov', 'manual'), 179 | ] 180 | 181 | # The name of an image file (relative to this directory) to place at the top of 182 | # the title page. 183 | #latex_logo = None 184 | 185 | # For "manual" documents, if this is true, then toplevel headings are parts, 186 | # not chapters. 187 | #latex_use_parts = False 188 | 189 | # Additional stuff for the LaTeX preamble. 190 | #latex_preamble = '' 191 | 192 | # Documents to append as an appendix to all manuals. 193 | #latex_appendices = [] 194 | 195 | # If false, no module index is generated. 196 | #latex_use_modindex = True 197 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Морфологический анализатор pymorphy 2 | =================================== 3 | 4 | Содержание: 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | 9 | intro 10 | usage/index 11 | algo 12 | ref/index 13 | CHANGES 14 | 15 | Указатели и поиск 16 | ================= 17 | 18 | * :ref:`modindex` 19 | * :ref:`search` 20 | 21 | -------------------------------------------------------------------------------- /docs/intro.rst: -------------------------------------------------------------------------------- 1 | pymorphy 2 | ======== 3 | 4 | Возможности библиотеки 5 | ---------------------- 6 | 7 | 1. Умеет приводит слово к нормальной форме (например, в ед.ч., 8 | И.п. для существительных):: 9 | 10 | >>> from pymorphy import get_morph 11 | >>> morph = get_morph('dicts/ru', 'sqlite') 12 | >>> print morph.normalize(u"ЛЮДЕЙ") 13 | ЧЕЛОВЕК 14 | 15 | 2. Умеет ставить слово в нужную форму. Например, ставить слово во множественное 16 | число, менять падеж слова и т.д.:: 17 | 18 | >>> print morph.inflect_ru(u"СУСЛИК", u"мн,рд") # много кого? 19 | СУСЛИКОВ 20 | 21 | Есть template filter, который позволяет делать это прямо в шаблоне django: 22 | 23 | .. code-block:: django 24 | 25 | {# в переменной animals "тридцать восемь попугаев и Удав" #} 26 | 27 | {% load pymorphy_tags %} 28 | {{ animals|inflect:"дт" }} захотелось пройтись по лесу. 29 | 30 | {# выведет "тридцати восьми попугаям и Удаву захотелось пройтись по лесу" #} 31 | 32 | 33 | 3. Умеет возвращать грамматическую информацию о слове (число, род, 34 | падеж, часть речи и т.д.). Делает это по словарю, для неизвестных 35 | слов работает предсказатель, если возможных форм несколько - возвращает 36 | несколько форм:: 37 | 38 | >>> info = morph.get_graminfo(u"БУТЯВКОВЕДАМИ") 39 | >>> print info[0]['norm'] # нормальная форма 40 | БУТЯВКОВЕД 41 | >>> print info[0]['class'] # часть речи, С = существительное 42 | C 43 | >>> print info[0]['info'] # род, число, падеж и т.д. 44 | мр,мн,тв 45 | 46 | 47 | Установка 48 | --------- 49 | 50 | 1. Устанавливаем pymorphy 51 | 52 | :: 53 | 54 | $ pip install pymorphy 55 | 56 | 2. Скачиваем словари. Для начала можно скачать 57 | `файл `_ 58 | с русскими словарями в sqlite. Подробнее о словарях можно почитать 59 | :doc:`тут`. 60 | 61 | 3. Распаковываем скачанный файл. 62 | 63 | 64 | Пример использования 65 | -------------------- 66 | 67 | :: 68 | 69 | from pymorphy import get_morph 70 | 71 | morph = get_morph('<путь/до/папки/в/которую/были/распакованы/скачанные/словари>', 'sqlite') 72 | 73 | #слова должны быть в юникоде и ЗАГЛАВНЫМИ 74 | info = morph.get_graminfo(u'ВАСЯ') 75 | 76 | 77 | Больше информации можно найти в :doc:`Руководстве`. 78 | 79 | .. note:: 80 | 81 | В pymorphy все, что не относится к django, можно использовать без django. 82 | 83 | Лицензия, полезные ссылки и т.д. 84 | -------------------------------- 85 | 86 | Лицензия - MIT. 87 | 88 | Должно работать на windows и \*nix-системах, python 2.6 и 2.7. 89 | Python 2.5 не поддерживается. 90 | 91 | Поддержка python 3.2 - экспериментальная: все должно работать с 92 | sqlite-словарями, но не быстро. 93 | 94 | * `Обсуждение`_ (тут можно задавать вопросы, делиться опытом, предлагать идеи) 95 | * `Сообщить об ошибке `_ 96 | * `Репозиторий с исходным кодом `_ 97 | 98 | Подключайтесь к разработке! Замечания, исправления, документация, патчи в любом 99 | виде всегда приветствуются. 100 | 101 | Если у вас какой-то вопрос, пишите о нем, по возможности, 102 | в `гугл-группу`_ - найденным ответом смогут потом воспользоваться другие люди. 103 | 104 | .. _Обсуждение: https://groups.google.com/forum/#!forum/pymorphy 105 | .. _гугл-группу: https://groups.google.com/forum/#!forum/pymorphy 106 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | set SPHINXBUILD=sphinx-build 6 | set BUILDDIR=_build 7 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 8 | if NOT "%PAPER%" == "" ( 9 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 10 | ) 11 | 12 | if "%1" == "" goto help 13 | 14 | if "%1" == "help" ( 15 | :help 16 | echo.Please use `make ^` where ^ is one of 17 | echo. html to make standalone HTML files 18 | echo. dirhtml to make HTML files named index.html in directories 19 | echo. pickle to make pickle files 20 | echo. json to make JSON files 21 | echo. htmlhelp to make HTML files and a HTML help project 22 | echo. qthelp to make HTML files and a qthelp project 23 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 24 | echo. changes to make an overview over all changed/added/deprecated items 25 | echo. linkcheck to check all external links for integrity 26 | echo. doctest to run all doctests embedded in the documentation if enabled 27 | goto end 28 | ) 29 | 30 | if "%1" == "clean" ( 31 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 32 | del /q /s %BUILDDIR%\* 33 | goto end 34 | ) 35 | 36 | if "%1" == "html" ( 37 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 38 | echo. 39 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 40 | goto end 41 | ) 42 | 43 | if "%1" == "dirhtml" ( 44 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 45 | echo. 46 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 47 | goto end 48 | ) 49 | 50 | if "%1" == "pickle" ( 51 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 52 | echo. 53 | echo.Build finished; now you can process the pickle files. 54 | goto end 55 | ) 56 | 57 | if "%1" == "json" ( 58 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 59 | echo. 60 | echo.Build finished; now you can process the JSON files. 61 | goto end 62 | ) 63 | 64 | if "%1" == "htmlhelp" ( 65 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 66 | echo. 67 | echo.Build finished; now you can run HTML Help Workshop with the ^ 68 | .hhp project file in %BUILDDIR%/htmlhelp. 69 | goto end 70 | ) 71 | 72 | if "%1" == "qthelp" ( 73 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 74 | echo. 75 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 76 | .qhcp project file in %BUILDDIR%/qthelp, like this: 77 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\pymorphy.qhcp 78 | echo.To view the help file: 79 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\pymorphy.ghc 80 | goto end 81 | ) 82 | 83 | if "%1" == "latex" ( 84 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 85 | echo. 86 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 87 | goto end 88 | ) 89 | 90 | if "%1" == "changes" ( 91 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 92 | echo. 93 | echo.The overview file is in %BUILDDIR%/changes. 94 | goto end 95 | ) 96 | 97 | if "%1" == "linkcheck" ( 98 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 99 | echo. 100 | echo.Link check complete; look for any errors in the above output ^ 101 | or in %BUILDDIR%/linkcheck/output.txt. 102 | goto end 103 | ) 104 | 105 | if "%1" == "doctest" ( 106 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 107 | echo. 108 | echo.Testing of doctests in the sources finished, look at the ^ 109 | results in %BUILDDIR%/doctest/output.txt. 110 | goto end 111 | ) 112 | 113 | :end 114 | -------------------------------------------------------------------------------- /docs/ref/Morph_UNIX.rst: -------------------------------------------------------------------------------- 1 | Описание оригинальной библиотеки с aot.ru (может оказаться полезным) 2 | -------------------------------------------------------------------- 3 | 4 | .. warning:: 5 | 6 | The following text is a description of the original aot.ru 7 | POS tagger implementation. Pymorphy was based on aot.ru research and it uses 8 | aot.ru dictionaries, but the implementation and further development is 9 | independent. The following text is provided only for reference. 10 | It does not describe how pymorphy works. 11 | 12 | This is a program of morphological analysis (Russian, German, and English languages). 13 | 14 | This program is distributed under the Library GNU Public Licence, which is in the file 15 | COPYING. 16 | 17 | This program was written by Andrey Putrin, Alexey Sokirko. 18 | The project started in Moscow in Dialing 19 | Company (Russian and English language). The German part was created 20 | at Berlin-Brandenburg Academy of Sciences and Humanities in Berlin (the project DWDS). 21 | 22 | The Russian lexicon is based upon Zaliznyak's Dictionary . 23 | The German lexicon is based upon Morphy system (http://www-psycho.uni-paderborn.de/lezius/). 24 | The English lexicon is based upon Wordnet. 25 | 26 | The project uses a regular expression library "PCRE" (Perl Compatible Regular Expressions). 27 | We test compilation only with version 6.4. Other versions were not tested. 28 | One should download this version from the official site and install it 29 | to the default place. If you do not want to install it or you do not have enough 30 | rights to do it, then you should create two environment variables: 31 | 32 | 1. RML_PCRE_LIB, that points to PCRE library directory, where 33 | libpcre.a and libpcrecpp.a should be located, for example:: 34 | 35 | export RML_PCRE_LIB=~/RML/contrib/pcre-6.4/.libs 36 | 37 | 2. RML_PCRE_INCLUDE, that points to PCRE include catalog, 38 | where "pcrecpp.h" is located, for example:: 39 | 40 | export RML_PCRE_INCLUDE=~/RML/contrib/pcre-6.4 41 | 42 | 43 | The system has been developed under Windows 2000 (MS VS 6.0), but 44 | has also been compiled and run under Linux(GCC). It should work with 45 | minor changes on other systems. 46 | 47 | Website of DDC: www.aot.ru, https://sf.net/projects/morph-lexicon/ 48 | 49 | I compiled all sources with gcc 3.2. Lower versions are not supported. 50 | 51 | 52 | Contents of the this source archive 53 | 54 | 1. The main morphological library (Source/LemmatizerLib). 55 | 2. Library for grammatical codes (Source/AgrgamtabLib). 56 | 3. Test morphological program (Source/TestLem).. 57 | 4. Library for working with text version of the dictionaries (Source/MorphWizardLib). 58 | 5. Generator of morphological prediction base (Source/GenPredIdx). 59 | 6. Generator of binary format of the dictionaries (Source/MorphGen). 60 | 61 | 62 | Installation 63 | ############ 64 | 65 | Unpacking 66 | ^^^^^^^^^ 67 | 68 | * Create a catalog and register a system variable RML, which points to this catalog:: 69 | 70 | mkdir /home/sokirko/RML 71 | export RML=/home/sokirko/RML 72 | 73 | * Put "lemmatizer.tar.gz", "???-src-morph.tar.gz" to this catalog, 74 | "???" can be "rus", "ger" or "eng" according to what you have downloaded. 75 | Unpack it:: 76 | 77 | tar xfz lemmatizer.tar.gz 78 | tar xfz ???-src-morph.tar.gz 79 | 80 | 81 | Compiling morphology 82 | ^^^^^^^^^^^^^^^^^^^^ 83 | 84 | 0. Do not forget to set RML_PCRE (see above) 85 | 1. cd $RML 86 | 2. ./compile_morph.sh 87 | This step should create all libraries and a test program $RML\Bin\TestLem. 88 | 89 | 90 | Building Morphological Dictionary 91 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 92 | 93 | 1. cd $RML 94 | 95 | 2. ./generate_morph_bin.sh 96 | where can be Russian, German according to the dictionary 97 | yo have downloaded. 98 | 99 | The script should terminate with message "Everything is OK". 100 | You can test the morphology:: 101 | 102 | $RML\Bin\TestLem 103 | 104 | 105 | If something goes wrong, write me to sokirko@yandex.ru. 106 | 107 | .. _mrd-file: 108 | 109 | MRD-file 110 | ######## 111 | 112 | This section describes the format of a mrd-file. Mrd-file is a text 113 | file which contains one morphological dictionary for one natural language. 114 | MRD is an abbreviation of "morphological dictionary". 115 | 116 | The usual place for this file is:: 117 | 118 | $RML/Dicts/SrcMorph/xxxSrc/morphs.mrd 119 | 120 | where xxx can be "Eng", "Rus" or "Ger" depending on the language. 121 | 122 | The encoding of the file depends also upon the language: 123 | 124 | * Russian - Windows 1251 125 | * German - Windows 1252 126 | * English - ASCII 127 | 128 | 129 | Gramtab-files 130 | ############# 131 | 132 | A mrd-file refers to a gramtab-file, which is 133 | language-dependent and which contains all possible full morphological 134 | patterns for the words. One line in a gramtab-file looks like as follows:: 135 | 136 | 137 | 138 | An ancode is an ID, which consists of two letters and which uniquely 139 | identifies a morphological pattern. A morphological pattern consists of 140 | and . For example, here is a line from the English 141 | gramtab:: 142 | 143 | te 1 VBE prsa,pl 144 | 145 | Here "te" is an ancode, "VBE" is a part of speech, "prsa,pl" are grammems, 146 | "1" is the obsolete unused number. 147 | 148 | In mrd-files we use ancodes to refer to a morphological pattern. 149 | 150 | Here is the list of all gramtab-files: 151 | 152 | * Russian - $Rml/Dicts/Morph/rgramtab.tab 153 | * German - $Rml/Dicts/Morph/ggramtab.tab 154 | * English - $Rml/Dicts/Morph/egramtab.tab 155 | 156 | 157 | 158 | Common information 159 | ################## 160 | 161 | All words in a mrd-file are written in uppercase. 162 | 163 | One mrd-file consists of the following sections: 164 | 165 | 1. Section of flexion and prefix models; 166 | 2. Section of accentual models; 167 | 3. Section of user sessions; 168 | 4. Section of prefix sets; 169 | 5. Section of lemmas. 170 | 171 | Each section is a set of records, one per line. The number of all records 172 | of the section is written in the very beginning of the section at 173 | a separate line. For example, here is a possible variant 174 | of the section of user sessions:: 175 | 176 | 1 177 | alex;17:10, 13 October 2003;17:12, 13 October 2003 178 | 179 | "1" means that this section contains only one record, which is written 180 | on the next line, thus this section contains only two lines. 181 | 182 | Section of possible flexion and prefix models 183 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 184 | 185 | Each record of this section is a list of items. Each item 186 | describes how one word form in a paradigm should be built. The whole list 187 | describes the whole paradigm (a set of word forms with morphological patterns). 188 | 189 | The format of one item is the following:: 190 | 191 | %* 192 | 193 | or:: 194 | 195 | %** 196 | 197 | where 198 | is a flexion (a string, which should be added to right of the word base) 199 | is a prefix (a string, which should be added to left of the word base) 200 | is an ancode. 201 | 202 | Let us consider an example of an English flexion and prefix model:: 203 | 204 | %F*na%VES*nb 205 | 206 | Here we have two items:: 207 | 208 | 1. = F; = na 209 | 2. = VES; = nb 210 | 211 | In order to decipher ancodes we should go the English gramtab-file. 212 | There we can find the following lines:: 213 | 214 | na NOUN narr,sg 215 | nb NOUN narr,pl 216 | 217 | If base "lea" would be ascribed to this model, then its paradigm would be the following:: 218 | 219 | leaf NOUN narr,sg 220 | leaves NOUN narr,pl 221 | 222 | It is important, that each word of a morphological dictionary should contain a 223 | reference to a line in this section. 224 | 225 | 226 | Section of possible accentual models 227 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 228 | 229 | Each record of this section is a comma-delimited list of numbers, where 230 | each number is an index of a stressed vowel of a word form(counting 231 | from the end). The whole list contains a position for each word 232 | form in the paradigm. 233 | 234 | If an item of an accentual model of word is equal to 255, then it 235 | is undefined, and it means that this word form is unstressed. 236 | 237 | Each word in the dictionary should have a reference to 238 | an accentual model, even though this model can consist only of empty items. 239 | 240 | For one word, the number and the order of items in the accentual model 241 | should be equal to the number and the order of items in the flexion and 242 | prefix model. For example we can ascribe to word "leaf" with the paradigm:: 243 | 244 | leaf NOUN narr,sg 245 | leaves NOUN narr,pl 246 | 247 | the following accentual model:: 248 | 249 | 2,3 250 | 251 | It produces the following accented paradigm:: 252 | 253 | le'af NOUN narr,sg 254 | le'aves NOUN narr,pl 255 | 256 | Section of user section 257 | ^^^^^^^^^^^^^^^^^^^^^^^ 258 | 259 | This is a system section, which contains information about user edit 260 | sessions. 261 | 262 | 263 | Section of prefix sets 264 | ^^^^^^^^^^^^^^^^^^^^^^ 265 | 266 | Each record of this section is a comma-delimited list of strings, where 267 | each string is a prefix, which can be prefixed to the whole word. If a prefix 268 | set is ascribed to a word, it means, that the words with these prefixes 269 | can also exist in the language. For example, if "leaf" has 270 | the prefix set "anti,contra", it follows the existence of words "antileaf", 271 | "contraleaf". 272 | 273 | A flexion and prefix model can contain 274 | also a reference to a prefix, but this prefix is for 275 | one separate word form, while a prefix set is ascribed to the whole word 276 | paradigm. 277 | 278 | 279 | Section of lemmas 280 | ^^^^^^^^^^^^^^^^^ 281 | 282 | A record of this section is a space-separated tuple of the following format:: 283 | 284 | 285 | 286 | where 287 | 288 | is a base (a constant part of a word in its paradigm) 289 | 290 | is an index of a flexion and prefix model 291 | 292 | is an index of an accentual model 293 | 294 | is an index of the session, by which the last user edited this word 295 | 296 | is ancode, which is ascribed to the whole word (intended: 297 | the common part of grammems in the paradigm) "-" if it is undefined 298 | 299 | is an index of a prefix set, or "-" if it is undefined 300 | 301 | -------------------------------------------------------------------------------- /docs/ref/gram_info_ru.rst: -------------------------------------------------------------------------------- 1 | 2 | .. _parameter-format: 3 | 4 | Обозначения для грамматической информации в pymorphy 5 | ---------------------------------------------------- 6 | 7 | Реализовано 2 формата выдачи результатов: формат по умолчанию и упрощенный 8 | стандартизованный формат, согласованный на конференции ДИАЛОГ-2010. 9 | 10 | Полный формат 11 | ^^^^^^^^^^^^^ 12 | 13 | Это формат по умолчанию. 14 | 15 | Обозначения соответствуют тем, что описаны тут: 16 | http://www.aot.ru/docs/rusmorph.html 17 | 18 | При указании в качстве параметров к методам их следует указывать через 19 | запятую без пробелов, порядок - произвольный, регистр учитывается:: 20 | 21 | "им,мр" 22 | 23 | Часть речи обычно идет отдельным параметром и не передается в строках с грам. 24 | информацией. 25 | 26 | .. _Russian-cases: 27 | 28 | Краткая справка по падежам 29 | ########################## 30 | 31 | :им: Именительный (Кто? Что?) 32 | :рд: Родительный (Кого? Чего?) 33 | :дт: Дательный (Кому? Чему?) 34 | :вн: Винительный (Кого? Что?) 35 | :тв: Творительный (Кем? Чем?) 36 | :пр: Предложный (О ком? О чём? и т.п.) 37 | :зв: Звательный (Его формы используются при обращении к человеку - им. падеж: Аня — звательный: Ань!) 38 | 39 | 40 | Все используемые граммемы 41 | ######################### 42 | 43 | :мр, жр, ср: мужской, женский, средний род 44 | :од, но: одушевленность, неодушевленность 45 | :ед, мн: единственное, множественное число 46 | :им, рд, дт, вн, тв, пр, зв: падежи (см. :ref:`информацию по падежам`) 47 | :2: обозначает второй родительный или второй предложный падежи 48 | :св, нс: совершенный, несовершенный вид 49 | :пе, нп: переходный, непереходный глагол 50 | :дст, стр: действительный, страдательный залог 51 | :нст, прш, буд: настоящее, прошедшее, будущее время 52 | :пвл: повелительная форма глагола 53 | :1л, 2л, 3л: первое, второе, третье лицо 54 | :0: неизменяемое 55 | :кр: краткость (для прилагательных и причастий) 56 | :сравн: сравнительная форма (для прилагательных) 57 | :имя, фам, отч: имя, фамилия, отчество 58 | :лок, орг: локативность, организация 59 | :кач: качественное прилагательное 60 | :вопр,относ: вопросительность и относительность (для наречий) 61 | :дфст: слово обычно не имеет множественного числа 62 | :опч: частая опечатка или ошибка 63 | :жарг, арх, проф: жаргонизм, архаизм, профессионализм 64 | :аббр: аббревиатура 65 | :безл: безличный глагол 66 | 67 | 68 | Части речи 69 | ########## 70 | 71 | ============== ================= ================== 72 | Части речи Пример Расшифровка 73 | ============== ================= ================== 74 | C мама существительное 75 | П красный прилагательное 76 | МС он местоимение-существительное 77 | Г идет глагол в личной форме 78 | ПРИЧАСТИЕ идущий причастие 79 | ДЕЕПРИЧАСТИЕ идя деепричастие 80 | ИНФИНИТИВ идти инфинитив 81 | МС-ПРЕДК нечего местоимение-предикатив 82 | МС-П всякий местоименное прилагательное 83 | ЧИСЛ восемь числительное (количественное) 84 | ЧИСЛ-П восьмой порядковое числительное 85 | Н круто наречие 86 | ПРЕДК интересно предикатив 87 | ПРЕДЛ под предлог 88 | СОЮЗ и союз 89 | МЕЖД ой междометие 90 | ЧАСТ же, бы частица 91 | ВВОДН конечно вводное слово 92 | КР_ПРИЛ красива краткое прилагательное 93 | КР_ПРИЧАСТИЕ построена краткое причастие 94 | ПОСЛ пословица 95 | ФРАЗ 96 | ============== ================= ================== 97 | 98 | Упрощенный формат 99 | ^^^^^^^^^^^^^^^^^ 100 | 101 | Данные в этом формате возвращает функция get_graminfo, вызванная с параметром 102 | standard=True. Формат был согласован на конференции Диалог-2010. 103 | 104 | .. note:: 105 | 106 | Получение результатов в этом формате НЕ быстрее, чем в полном. 107 | Разбор "внутри" все равно идет в "полном" формате, 108 | и лишь перед выводом данные преобразуются в упрощенный. 109 | 110 | Части речи 111 | ########## 112 | 113 | Для разметки используется упрощенная система частей речи: 114 | 115 | :S: существительное (яблоня, лошадь, корпус, вечность) 116 | :A: прилагательное (коричневый, таинственный, морской) 117 | :V: глагол (пользоваться, обрабатывать) 118 | :PR: предлог (под, напротив) 119 | :CONJ: союз (и, чтобы) 120 | :ADV: — прочие не няемые слова (частицы, междометия, вводные слова) 121 | 122 | Могут быть размечены любым образом: 123 | 124 | :Местоимения: (включая наречные и предикативные) 125 | :Числительные: 126 | 127 | Морфология (грамматические_признаки) 128 | #################################### 129 | 130 | В категориях ADV,PR,CONJ поле остается пустым. Морфология указывается 131 | только для S,A,V. 132 | 133 | Здесь также используется сокращенный набор признаков: 134 | 135 | :род: m, f, n 136 | :падеж: nom, gen, dat, acc, ins, loc 137 | :число: sg, pl 138 | :время/наклонение/причастие/деепричастие: pres, past, imper, inf, partcp, ger 139 | :залог: act, pass 140 | :лицо: 1p, 2p, 3p 141 | -------------------------------------------------------------------------------- /docs/ref/index.rst: -------------------------------------------------------------------------------- 1 | Справка по API 2 | ============== 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | storages 8 | gram_info_ru 9 | morph 10 | storage_backends 11 | Morph_UNIX 12 | -------------------------------------------------------------------------------- /docs/ref/morph.rst: -------------------------------------------------------------------------------- 1 | Морфологический анализатор 2 | ========================== 3 | 4 | .. note:: 5 | 6 | Этот раздел справки сгенерирован автоматически. 7 | 8 | .. automodule:: pymorphy._morph 9 | 10 | .. autofunction:: get_morph 11 | 12 | .. autoclass:: Morph 13 | :members: 14 | :undoc-members: 15 | 16 | .. automethod:: __init__ 17 | -------------------------------------------------------------------------------- /docs/ref/storage_backends.rst: -------------------------------------------------------------------------------- 1 | Key-value - бэкенды 2 | ------------------- 3 | 4 | .. note:: 5 | 6 | Этот раздел справки сгенерирован автоматически. 7 | 8 | Ниже описаны скорее детали реализации. Чтоб использовать pymorphy, их знать 9 | необязательно. 10 | 11 | Если вы не планируете участвовать в разработке pymorphy, полезнее ознакомиться 12 | со следующим документом: :doc:`storages` 13 | 14 | .. automodule:: pymorphy.backends 15 | :members: 16 | :undoc-members: 17 | :show-inheritance: 18 | 19 | Базовый класс 20 | ^^^^^^^^^^^^^ 21 | 22 | .. automodule:: pymorphy.backends.base 23 | :members: 24 | :undoc-members: 25 | :show-inheritance: 26 | 27 | 28 | DB-источники данных 29 | ^^^^^^^^^^^^^^^^^^^ 30 | 31 | .. automodule:: pymorphy.backends.shelve_source 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | 36 | 37 | Интерфейс к SQLite 38 | """"""""""""""""""""""""""""""""""""" 39 | 40 | .. automodule:: pymorphy.backends.shelve_source.sqlite_shelve 41 | :members: 42 | :undoc-members: 43 | :show-inheritance: 44 | 45 | 46 | 47 | Интерфейс к CDB 48 | """"""""""""""" 49 | 50 | .. automodule:: pymorphy.backends.shelve_source.tinycdb_shelve 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | 55 | 56 | Бэкенд для разбора исходных MRD-файлов 57 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 58 | 59 | Алгоритм работы с ним "как есть" должен быть совсем не таким, 60 | как в pymorphy, pymorphy с исходными MRD-файлами работает крайне неэффективно. 61 | 62 | Этот бэкенд используется только для переконвертации исходных словарей. 63 | 64 | .. automodule:: pymorphy.backends.mrd_source 65 | :members: 66 | :undoc-members: 67 | :show-inheritance: 68 | 69 | 70 | Pickle-источник данных 71 | ^^^^^^^^^^^^^^^^^^^^^^ 72 | 73 | .. automodule:: pymorphy.backends.pickle_source 74 | :members: 75 | :undoc-members: 76 | :show-inheritance: 77 | 78 | -------------------------------------------------------------------------------- /docs/ref/storages.rst: -------------------------------------------------------------------------------- 1 | Поддерживаемые типы хранилищ 2 | ============================ 3 | 4 | .. _supported-storages: 5 | 6 | Словари лежат тут: https://bitbucket.org/kmike/pymorphy/downloads/ и 7 | называются по формуле ``<язык>.<тип базы>-<формат данных>.zip``. 8 | 9 | .. warning:: 10 | 11 | Не скачивайте словари вида ``<язык>.<тип базы>.zip`` (например, 12 | ``ru.sqlite.zip`` - без 'json'), эти словари устаревшие: работают 13 | медленнее, в sqlite-словарях серьезная ошибка. В разделе для скачивания 14 | они пока оставлены в целях совместимости. 15 | 16 | 17 | Типы хранилищ 18 | ------------- 19 | 20 | SQLite 21 | ^^^^^^ 22 | 23 | Файлы со словарями имеют расширение "``.sqlite``". Набор словарей 24 | для русского языка: `ru.sqlite-json.zip `_. 25 | Пример подключения:: 26 | 27 | m = get_morph('dicts/ru', 'sqlite') 28 | 29 | Преимущество - в совместимости. Не требует установки, кроссплатформенный формат 30 | хранения данных. Если какие-то проблемы с использованием других 31 | вариантов, можно использовать SQLite. Вариант по умолчанию. 32 | 33 | Этот самый медленный вариант. 34 | 35 | 36 | CDB 37 | ^^^ 38 | 39 | Файлы со словарями имеют расширение "``.cdb``". Набор словарей 40 | для русского языка: `ru.cdb-json.zip `_. 41 | 42 | Поддерживается 2 варианта работы с этими словарями: через библиотеки python-cdb и tinycdb. 43 | Для установки каждой из библиотек потребуются установленные средства сборки (gcc, заголовочные файлы питона). 44 | 45 | python-cdb быстрее, требует больше памяти и (осторожно!) распространяется по лицензии GPL. 46 | 47 | Установка:: 48 | 49 | $ pip install python-cdb 50 | 51 | Пример подключения:: 52 | 53 | m = get_morph('dicts/ru', 'python-cdb') 54 | 55 | tinycdb медленнее, требует меньше памяти и распространяется по лицензии MIT + Public Domain. 56 | 57 | Установка:: 58 | 59 | $ pip install tinycdb 60 | 61 | Пример подключения:: 62 | 63 | m = get_morph('dicts/ru', 'tinycdb') 64 | 65 | 66 | Выбор хранилища 67 | --------------- 68 | 69 | * Хочется быстро все попробовать, не заморачиваясь за установку: SQLite. 70 | * Нужна большая скорость: CDB. 71 | * Нужна максимальная скорость: используем pickle (осторожно, потребуется 72 | 200-300Мб оперативной памяти). 73 | 74 | Кеширование сильно ускоряет работу и включено по умолчанию, но оно увеличивает 75 | потребление памяти в соответствии с тем, сколько разных парадигм и правил 76 | было запрошено. 77 | 78 | Общая информация 79 | ---------------- 80 | 81 | Алгоритмы в pymorphy устроены так, что требуют для работы данные в виде 82 | словарей и массивов. Изначально pymorphy использовал структуры 83 | ``list`` и ``dict`` для хранения данных о словах и словообразовании. 84 | 85 | Это работало быстро, но требовало большого количества оперативной памяти. 86 | Поэтому сейчас для того, чтобы не загружать все словари сразу в память, 87 | данные берутся из одной из key-value базы данных. 88 | 89 | .. note:: 90 | 91 | Интерфейс доступа к key-value хранилищам при этом остался тем же. 92 | Т.е. требование к хранилищу (обертке над хранилищем) - притворяться 93 | массивом или словарем, а именно - поддерживать ``[]`` и ``in`` 94 | (``__getitem__``, ``__setitem__`` и ``__contains__``). 95 | 96 | -------------------------------------------------------------------------------- /docs/usage/base.rst: -------------------------------------------------------------------------------- 1 | Использование 2 | ------------- 3 | 4 | .. py:currentmodule:: pymorphy._morph 5 | 6 | Подготовка 7 | ^^^^^^^^^^ 8 | 9 | Чтобы использовать морфологический анализатор, нужно сначала создать объект 10 | класса :class:`pymorphy.Morph `:: 11 | 12 | from pymorphy import get_morph 13 | morph = get_morph('dicts/ru', 'sqlite') 14 | 15 | Аргументы :meth:`pymorphy.get_morph `: 16 | 17 | * ``path`` - обязательный параметр, путь до папки с файлами; 18 | * ``backend`` - используемое key-value хранилище ('sqlite' или 'cdb'); 19 | * ``cached`` - использовать ли кэш (True по умолчанию). 20 | 21 | Можно также передавать любые дополнительные аргументы, которые принимает 22 | конструктор класса :class:`pymorphy.Morph `. 23 | 24 | Вместо явной передачи параметров ``path`` и ``backend`` можно использовать 25 | переменные окружения ``PYMORPHY_DICTIONARY_PATH`` и ``PYMORPHY_DICTIONARY_BACKEND``, 26 | например (в shell): 27 | 28 | .. code-block:: bash 29 | 30 | $ export PYMORPHY_DICTIONARY_PATH = /usr/share/dicts/ru 31 | $ export PYMORPHY_DICTIONARY_BACKEND = cdb 32 | 33 | и потом:: 34 | 35 | from pymorphy import get_morph 36 | morph = get_morph() 37 | 38 | .. note:: 39 | 40 | Обратите внимание, все методы Morph ожидают, что строковые 41 | аргументы (в.т.ч. пустые или латинские, если используется pymorphy-speedups) 42 | - это unicode-строки. Кроме того, слова для обработки должны быть в верхнем 43 | регистре. 44 | 45 | .. _resource-warning: 46 | .. warning:: 47 | 48 | Всегда старайтесь использовать единственный экземпляр анализатора. 49 | 50 | Объекты класса :class:`pymorphy.Morph ` требуют довольно много 51 | ресурсов для создания, не уничтожаются сборщиком мусора и не закрывают 52 | за собой файловые дескрипторы, поэтому постоянное создание 53 | анализаторов будет приводить к утечке ресурсов. 54 | 55 | 56 | Получение информации о слове 57 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 58 | 59 | >>> word = u'Вася'.upper() 60 | >>> info = morph.get_graminfo(word)[0] 61 | >>> print info['norm'] 62 | ВАСЯ 63 | >>> print info['class'] 64 | С 65 | >>> print info['info'] 66 | мр,имя,ед,им 67 | >>> print info['method'] 68 | lemma(ВАС).suffix(Я) 69 | 70 | :meth:`Morph.get_graminfo` возвращает list всех возможных вариантов разбора 71 | слова. Каждый вариант разбора - dict, в котором есть нормальная форма, часть 72 | речи, грамматическая информация и служебные данные для отладки. См. также 73 | :doc:`/ref/gram_info_ru`. 74 | 75 | 76 | Получение нормальных форм 77 | ^^^^^^^^^^^^^^^^^^^^^^^^^ 78 | 79 | >>> morph.normalize(u'БУТЯВКАМ') 80 | set(u'БУТЯВКА') 81 | 82 | :meth:`Morph.normalize` возвращает множество (set) всех возможных нормальных 83 | форм слова. 84 | 85 | Склонение 86 | ^^^^^^^^^ 87 | 88 | >>> morph.inflect_ru(u'БУТЯВКА', u'дт,мн') 89 | БУТЯВКАМ 90 | 91 | :meth:`Morph.inflect_ru` возвращает слово в форме, которая соответствует 92 | переданной и меньше всего отличается от исходной. В случае, если такую форму 93 | найти не удается, возвращается исходное слово. 94 | 95 | .. note:: 96 | 97 | Этот метод на данный момент не работает с фамилиями. 98 | См. :ref:`names-inflection`. 99 | 100 | См. также: :doc:`/ref/gram_info_ru` 101 | 102 | 103 | Постановка во множественное число 104 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 105 | 106 | Простое:: 107 | 108 | >>> morph.pluralize_ru(u'БУТЯВКЕ') 109 | БУТЯВКАМ 110 | 111 | Согласованное с цифрой:: 112 | 113 | >>> morph.pluralize_inflected_ru(u'ПОПУГАЙ', 1) 114 | ПОПУГАЙ 115 | >>> morph.pluralize_inflected_ru(u'ПОПУГАЙ', 2) 116 | ПОПУГАЯ 117 | >>> morph.pluralize_inflected_ru(u'ПОПУГАЙ', 38) 118 | ПОПУГАЕВ 119 | 120 | См. :meth:`Morph.pluralize_ru`, :meth:`Morph.pluralize_inflected_ru`. 121 | 122 | .. _django-integration: 123 | 124 | -------------------------------------------------------------------------------- /docs/usage/contrib.rst: -------------------------------------------------------------------------------- 1 | Дополнительные возможности 2 | ========================== 3 | 4 | Разбиение текста на слова и токены 5 | ---------------------------------- 6 | 7 | .. automodule:: pymorphy.contrib.tokenizers 8 | 9 | .. autofunction:: extract_tokens 10 | 11 | .. autofunction:: extract_words 12 | 13 | .. note:: 14 | 15 | См. также `nltk.tokenize`_. 16 | 17 | .. _nltk.tokenize: https://github.com/nltk/nltk/tree/master/nltk/tokenize 18 | 19 | 20 | Восстановление регистра слов 21 | ---------------------------- 22 | 23 | После обработки слов морфологическим анализатором может теряться информация о 24 | том, как это слово было написано в оригинале (заглавные-строчные буквы). 25 | Чтоб восстановить эту информацию, можно воспользоваться функцией 26 | :func:`pymorphy.contrib.word_case.restore_word_case`. 27 | 28 | 29 | .. automodule:: pymorphy.contrib.word_case 30 | 31 | .. autofunction:: restore_word_case 32 | 33 | 34 | .. _names-inflection: 35 | 36 | Склонение имен и фамилий 37 | ------------------------ 38 | 39 | .. py:currentmodule:: pymorphy.contrib.lastnames_ru 40 | 41 | Склонение фамилий 42 | ^^^^^^^^^^^^^^^^^ 43 | 44 | При разборе текста фамилии часто неотличимы (без дополнительной информации) 45 | от других слов: "много козлов" - "василий козлов". При этом фамилии склоняются 46 | по другим правилам, не как обычные слова, поэтому морфологическому 47 | анализатору для правильной работы необходимо: 48 | 49 | * знать, что слово - фамилия; 50 | * уметь склонять фамилии по специальным правилам. 51 | 52 | В pymorphy реализована вторая часть: склонять фамилии можно с помощью функций 53 | из модуля :mod:`pymorphy.contrib.lastnames_ru`, который реализует правила 54 | склонения фамилий, описанные на 55 | `gramota.ru `_. 56 | 57 | .. warning:: 58 | 59 | Работа с фамилиями в pymorphy сейчас экспериментальная, и не все методы 60 | обычного анализатора доступны (в частности, не завершена работа над 61 | постановкой фамилий во множественную форму), API может поменяться в 62 | последующих релизах. 63 | 64 | Склонение фамилий использует алгоритм, похожий на 65 | :ref:`алгоритм ` предсказания склонения слов, 66 | отсутствующих в словаре. 67 | 68 | Угадывание леммы и суффикса 69 | """"""""""""""""""""""""""" 70 | 71 | Если слово содержит суффикс, характерный для фамилии (например, 72 | суффикс -ов- характерен для русских фамилий "Усов", "Хвостов", "Котов"), 73 | то часть до суффикса принимается за лемму, часть после суффикса 74 | отбрасывается, и лемма плюс суффикс склоняются. 75 | 76 | Например, слово "Петровичу" будет разделено на составляющие "петров", "-ич-" и 77 | "у". Где "петров" - лемма, "-ич-" - суффикс фамилии, а "у" будет 78 | проигнорировано. Согласно правилам склонения фамилий с суффиксом -ич-, 79 | слово "петров[-ич-]" будет склонено в шести падежах как "петрович", 80 | "петровича", "петровичу"... для мужского рода, и "петрович", "петрович", 81 | "петрович"... для женского. 82 | 83 | Нормализация фамилий 84 | """""""""""""""""""" 85 | 86 | На базе этого построена нормализация фамилий: нормальная форма получается 87 | только после выделения леммы и суффикса во время попытки склонения. Получив 88 | возможные варианты во всех падежах, мы можем проверить, что а) входное слово 89 | "петровичу" может склоняться как фамилия; б) исходное слово - это дательный 90 | падеж фамилии; и принять именительный падеж ("петрович") за нормальную форму, 91 | если оба условия выполняются. 92 | 93 | Если из входного слова могут быть выделены лемма и суффикс, но в то же время 94 | оно не является вариантом склонения этой фамилии, то это расценивается как 95 | ложное срабатывание. Пример: слово "кроссовый" хоть и подходит под шаблон 96 | фамилии "кросс[-ов-]", но, по правилам склонения фамилий, не может быть её 97 | производным. 98 | 99 | Если :func:`decline` не нашла характерных признаков фамилии, входное слово 100 | будет склонено как обычное (не фамилия) соответствующего рода в именительном 101 | падеже. Например, в случае нормализации слова "кроссового", вызов 102 | ``lastnames_ru.normalize(morph, u'КРОССОВОГО', u'мр')`` будет аналогичен 103 | вызову ``morph.inflect_ru(u'КРОССОВОГО', u'им,ед,мр')``. 104 | 105 | Известные ограничения 106 | """"""""""""""""""""" 107 | 108 | В некоторых случаях невозможно точно определить нормальную форму из-за 109 | особенностей написания некоторых фамилий: Крамского, Достоевского. В первом 110 | случае нормальной формой должно быть "Крамской", во втором случае 111 | "Достоевский", но т.к. обе фамилии имеют суффикс -ск-, то будут склонены 112 | одинаково, и в первом случае именительным падежом будет "Крамский". 113 | 114 | .. automodule:: pymorphy.contrib.lastnames_ru 115 | 116 | .. autofunction:: decline 117 | 118 | .. autofunction:: normalize 119 | 120 | >>> from pymorphy.contrib import lastnames_ru 121 | >>> print lastnames_ru.normalize(morph, u'СУВОРОВУ', u'мр') 122 | СУВОРОВ 123 | >>> print lastnames_ru.normalize(morph, u'СУВОРОВУ', u'жр') 124 | СУВОРОВА 125 | 126 | .. autofunction:: inflect 127 | 128 | .. autofunction:: get_graminfo 129 | 130 | .. autofunction:: pluralize 131 | 132 | Если в gram_form явно указать род - это будет использовано как подсказка. 133 | 134 | >>> print lastnames_ru.pluralize(morph, u'СУВОРОВУ', u'') 135 | СУВОРОВЫМ 136 | >>> print lastnames_ru.pluralize(morph, u'СУВОРОВУ', u'жр') 137 | СУВОРОВЫХ 138 | >>> print lastnames_ru.pluralize(morph, u'СУВОРОВУ', u'жр,дт') 139 | СУВОРОВЫМ 140 | 141 | В первом случае :func:`pluralize` воспринял входную фамилию в мужском роде, 142 | дательном падеже (муской род принимается по-умолчанию если не указан женский). 143 | Во втором - как женскую фамилию в винительном падеже. В последнем случае 144 | подсказка была проигнорирована т.к. требуемый падеж указан явно. 145 | 146 | Указания на число ('ед' и 'мн') игнорируются. 147 | 148 | .. autofunction:: pluralize_inflected 149 | 150 | 151 | Склонение имен людей 152 | ^^^^^^^^^^^^^^^^^^^^ 153 | 154 | Имена, в отличие от фамилий, должны быть в словарях и склоняться 155 | стандартным морфологическим анализатором. Тонкость тут в том, что 156 | имена часто могут распознаваться еще и как другие части речи, и, 157 | чтобы склонение работало правильно, нужно явно указать 158 | желаемую часть речи (существительное):: 159 | 160 | >>> print morph.inflect_ru(u'КАТЯ', u'дт') # катя бочку 161 | КАТЯ 162 | >>> print morph.inflect_ru(u'КАТЯ', u'дт', u'С') # теперь правильно 163 | КАТЕ 164 | 165 | 166 | -------------------------------------------------------------------------------- /docs/usage/django.rst: -------------------------------------------------------------------------------- 1 | Интеграция с django 2 | ------------------- 3 | 4 | .. py:currentmodule:: pymorphy._morph 5 | 6 | Настройка 7 | ^^^^^^^^^ 8 | 9 | 1. Добавляем ``'pymorphy'`` в ``INSTALLED_APPS``. 10 | 11 | 2. Описываем в settings.py установленные словари:: 12 | 13 | PYMORPHY_DICTS = { 14 | 'ru': { 'dir': '/usr/share/pymorphy/ru' }, 15 | } 16 | 17 | более сложный пример:: 18 | 19 | PYMORPHY_DICTS = { 20 | 'ru': { 21 | 'dir': os.path.join([PROJECT_DIR, 'files', 'dicts']), 22 | 'backend': 'cdb', 23 | 'use_cache': False, 24 | }, 25 | } 26 | 27 | Параметры: 28 | 29 | * ``dir`` - обязательный параметр, путь до папки с файлами; 30 | * ``backend`` - используемое key-value хранилище ('sqlite' по умолчанию); 31 | * ``use_cache`` - использовать ли кэш (True по умолчанию). 32 | 33 | Шаблонные фильтры 34 | ^^^^^^^^^^^^^^^^^ 35 | 36 | Фильтры подключаются следующей командой: 37 | 38 | .. code-block:: django 39 | 40 | {% load pymorphy_tags %} 41 | 42 | .. _inflect-filter: 43 | 44 | inflect 45 | ####### 46 | 47 | Меняет грамматическую форму каждого слова на указанную в параметрах. 48 | Про доступные параметры можно почитать :ref:`тут (см. "Полный формат") `. 49 | 50 | Пример: 51 | 52 | .. code-block:: django 53 | 54 | {# в переменной city "Нижний Новгород" #} 55 | 56 | {% load pymorphy_tags %} 57 | Мы начали работу в {{ city|inflect:"пр" }}! 58 | 59 | {# выведет "Мы начали работу в Нижнем Новгороде!" #} 60 | 61 | 62 | Пример с несклоняемой частью 63 | 64 | .. code-block:: django 65 | 66 | {% load pymorphy_tags %} 67 | 68 | Не осталось у нас {{ "лошадь [[Пржевальского]]"|inflect:"рд,мн" }}. 69 | 70 | {# выведет "Не осталось у нас лошадей Пржевальского" #} 71 | 72 | .. _inflect_marked-filter: 73 | 74 | inflect_marked 75 | ############## 76 | 77 | Идентичен фильтру inflect за исключением того, что противоположным образом 78 | трактует [[ ]] 79 | 80 | .. code-block:: django 81 | 82 | {% load pymorphy_tags %} 83 | Не осталось у нас {{ "[[лошадь]] Пржевальского"|inflect_marked:"рд,мн" }}. 84 | 85 | {# выведет "Не осталось у нас лошадей Пржевальского" #} 86 | 87 | .. _plural-filter: 88 | 89 | plural 90 | ###### 91 | 92 | Ставит слово в форму, которая согласуется с заданным числом (1 попугай, 93 | 2 попугая, 5 попугаев). 94 | 95 | .. code-block:: django 96 | 97 | {% load pymorphy_tags %} 98 | 99 | {# в переменной num число попугаев (пусть = 38) #} 100 | На дереве {{ num }} {{ "попугай"|plural:num }}. 101 | {# выведет "На дереве 38 попугаев." #} 102 | 103 | {# в переменной animal - "лошадь" #} 104 | А еще есть {{ num }} {{ animal|plural:num }}. 105 | {# выведет "А еще есть 38 лошадей." #} 106 | 107 | Фильтры :ref:`inflect-filter` и :ref:`plural-filter` не склоняют все, 108 | что заключено в двойные квадратные скобки. Фильтр :ref:`inflect_marked-filter` 109 | - наоборот, работает только с тем, что в двойных квадратных скобках. 110 | 111 | Можно указать другие разделители (обязательно 2х-символьные), 112 | определив в settings.py переменные ``PYMORPHY_MARKER_OPEN`` и 113 | ``PYMORPHY_MARKER_CLOSE``. 114 | 115 | .. note:: 116 | 117 | Фильтры из pymorphy_tags стараются сохранить написание больших-маленьких 118 | букв (обрабатываются варианты "ВСЕ СЛОВО БОЛЬШИМИ", "С заглавной", 119 | "все маленькими"). 120 | 121 | Если по какой-то причине смена формы не удалась, возвращают исходную строку. 122 | 123 | .. warning:: 124 | 125 | Фильтры в настоящий момент могут плохо работать с именами и фамилиями. 126 | 127 | Получение экземпляра анализатора 128 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 129 | 130 | В случае, когда настроена интеграция pymorphy с django, 131 | экземпляр анализатора для использования в python-коде можно получать 132 | следующим образом:: 133 | 134 | from pymorphy.django_conf import default_morph as morph 135 | 136 | Не стоит получать экземпляр анализатора через 137 | :meth:`pymorphy.get_morph ` во вьюхах (или еще где-то на каждый 138 | запрос) - это приведет к утечке ресурсов. 139 | -------------------------------------------------------------------------------- /docs/usage/index.rst: -------------------------------------------------------------------------------- 1 | Руководство 2 | =========== 3 | 4 | Содержание: 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | 9 | base 10 | django 11 | speed 12 | contrib 13 | -------------------------------------------------------------------------------- /docs/usage/speed.rst: -------------------------------------------------------------------------------- 1 | Скорость 2 | -------- 3 | 4 | Скорость разбора 5 | ^^^^^^^^^^^^^^^^ 6 | 7 | С pymorphy можно ожидать разбор нескольких сотен русских слов в секунду 8 | "из коробки". После дополнительной настройки можно получить производительность в 9 | несколько тысяч слов в секунду. 10 | 11 | Этой скорости достаточно для многих задач (например, для различных экспериментов 12 | и задач, связанных с web), но pymorphy в нынешнем виде, думаю, не подойдет, 13 | если нужно быстро обрабатывать очень большие объемы данных. В этом случае 14 | лучше использовать `lemmatizer `_ или 15 | `mystem `_. 16 | 17 | У pymorphy нет цели быть быстрым, приоритет отдается качеству разбора и легкости 18 | сопровождения. С учетом того, что это хобби-opensource-проект, код и алгоритмы 19 | должны быть максимально простыми и понятными, чтобы облегчить внесение 20 | изменений и доработку под конкретные задачи. 21 | 22 | На данный момент pymorphy можно заставить работать быстрее несколькими способами: 23 | 24 | * перейти на более быстрое хранилище (sqlite → cdb → pickle); 25 | * отключить ненужные предсказатели; 26 | * установить simplejson (для упрощения установки pymorphy его не требует и 27 | использует по умолчанию встроенный медленный модуль):: 28 | 29 | $ pip install simplejson 30 | 31 | * поставить пакет `pymorphy-speedups `_, 32 | который содержит авто-подключаемое Cython-расширение:: 33 | 34 | $ pip install pymorphy-speedups 35 | 36 | .. note:: 37 | 38 | Для установки pymorphy-speedups и simplejson потребуются заголовочные файлы 39 | питона и среда с компилятором (как и для сборки любых других расширений). 40 | 41 | Выбор хранилища для словарей 42 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 43 | 44 | pymorphy поддерживает разные форматы для хранения словарей. Формат по 45 | умолчанию - sqlite. Этот формат поддерживается везде, не требует настройки, но, 46 | одновременно, является самым медленным. 47 | 48 | Более быстрая альтернативы - cdb (через библиотеки tinycdb или python-cdb); используйте ее, 49 | если есть возможность устанавливать пакеты с C-расширениями. tinycdb требует меньше оперативной 50 | памяти и не накладывает лицензионных ограничений; python-cdb быстрее и распространяется 51 | по лицензии GPL. 52 | 53 | Самый быстрый вариант - это загрузка словарей целиком в память (через 54 | pickle backend). В этом случае нет задержек на чтение данных с диска и 55 | преобразование их в нужный формат (все читается сразу), но 56 | расходуется 200-300Мб оперативной памяти. В этот формат словари можно 57 | преобразовать с помощью скрипта encode_dicts.py (лежит в репозитории с исходным 58 | кодом). 59 | 60 | Более подробно обо всем этом можно узнать тут: :doc:`/ref/storages`. 61 | 62 | -------------------------------------------------------------------------------- /encode_dicts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #coding: utf-8 3 | 4 | """ 5 | Скрипт кодирования словарей. Пока только руками его править, если какие-то 6 | настройки нужны. 7 | """ 8 | 9 | import codecs 10 | import os 11 | 12 | from pymorphy.backends import MrdDataSource, ShelveDataSource, PickleDataSource 13 | 14 | def _unlink(fn): 15 | try: 16 | os.unlink(fn) 17 | except OSError: 18 | pass 19 | 20 | def convert_file(in_file, out_file, in_charset, out_charset): 21 | with codecs.open(in_file, 'r', in_charset) as f: 22 | text = f.read() 23 | with codecs.open(out_file, 'w', out_charset ) as f: 24 | f.write(text) 25 | 26 | 27 | def load_mrd(dir): 28 | print('parsing source dictionary file...') 29 | mrd_source = MrdDataSource(os.path.join(dir,'morphs.utf8.mrd'), 30 | os.path.join(dir,'gramtab.utf8.mrd'), 31 | strip_EE=True) 32 | mrd_source.load() 33 | 34 | print('calculating rule frequencies...') 35 | mrd_source.calculate_rule_freq() 36 | return mrd_source 37 | 38 | 39 | def make_pickled(dest_dir, mrd): 40 | print('creating pickled dictionary...') 41 | name = os.path.join(dest_dir, 'morphs.pickle') 42 | _unlink(name) 43 | source = PickleDataSource(name) 44 | source.convert_and_save(mrd) 45 | source.load() 46 | source._check_self() 47 | mrd._check_other(source) 48 | 49 | 50 | def make_shelve(dest_dir, mrd, backend): 51 | print('creating %s dictionary...' % backend) 52 | 53 | for fn in ['lemmas', 'rules', 'endings', 'misc', 'freq']: 54 | _unlink(os.path.join(dest_dir, fn+'.'+backend)) 55 | _unlink(os.path.join(dest_dir, fn+'.'+backend+'.tmp')) 56 | 57 | try: 58 | source = ShelveDataSource(dest_dir, backend) 59 | source.convert_and_save(mrd) 60 | source.load() 61 | mrd._check_other(source) 62 | except ImportError: 63 | print "Backend %s is not available." % backend 64 | 65 | 66 | def convert_dicts(src_dir, dest_dir, lang): 67 | 68 | if lang == 'en': 69 | msg = "encoding English.." 70 | src_subdir = os.path.join('SrcMorph','EngSrc') 71 | coding = 'latin1' 72 | gramtab = 'egramtab.tab' 73 | elif lang == 'ru': 74 | msg = "encoding Russian.." 75 | src_subdir = os.path.join('SrcMorph','RusSrc') 76 | coding = 'cp1251' 77 | gramtab = 'rgramtab.tab' 78 | else: 79 | print "invalid language" 80 | return 81 | 82 | print msg 83 | convert_file(os.path.join(src_dir, src_subdir, 'morphs.mrd'), 84 | os.path.join(dest_dir, lang, 'morphs.utf8.mrd'), 85 | coding, 'utf8') 86 | 87 | convert_file(os.path.join(src_dir, 'Morph', gramtab), 88 | os.path.join(dest_dir, lang, 'gramtab.utf8.mrd'), 89 | coding, 'utf8') 90 | 91 | 92 | def cleanup_after_convert(dir): 93 | print('cleaning up...') 94 | os.unlink(os.path.join(dir, 'morphs.utf8.mrd')) 95 | os.unlink(os.path.join(dir, 'gramtab.utf8.mrd')) 96 | print("========") 97 | 98 | 99 | if __name__ == '__main__': 100 | MrdDataSource.setup_psyco() 101 | 102 | src_dir = 'dicts/src/Dicts' 103 | dest_dir = 'dicts/converted' 104 | 105 | LANGUAGES = 'en', 'ru' 106 | FORMATS = 'cdb', 'sqlite', # 'tinycdb', 'cdblib', # 'shelve', 107 | 108 | for lang in LANGUAGES: 109 | convert_dicts(src_dir, dest_dir, lang) 110 | dest = os.path.join(dest_dir, lang) 111 | 112 | mrd = load_mrd(dest) 113 | make_pickled(dest, mrd) 114 | 115 | for fmt in FORMATS: 116 | make_shelve(dest, mrd, fmt) 117 | 118 | cleanup_after_convert(dest) 119 | 120 | print "done." 121 | -------------------------------------------------------------------------------- /example.py: -------------------------------------------------------------------------------- 1 | #coding: utf8 2 | import re 3 | import pymorphy 4 | import pymorphy.utils 5 | 6 | text = u''' 7 | Сяпала Калуша по напушке и увазила бутявку. И волит: 8 | — Калушата, калушаточки! Бутявка! 9 | Калушата присяпали и бутявку стрямкали. И подудонились. 10 | А Калуша волит: 11 | — Оее, оее! Бутявка-то некузявая! 12 | Калушата бутявку вычучили. 13 | Бутявка вздребезнулась, сопритюкнулась и усяпала с напушки. 14 | А Калуша волит: 15 | — Бутявок не трямкают. Бутявки дюбые и зюмо-зюмо некузявые. От бутявок дудонятся. 16 | А бутявка волит за напушкой: 17 | — Калушата подудонились! Калушата подудонились! Зюмо некузявые! Пуськи бятые! 18 | ''' 19 | 20 | r = re.compile('[\W+-]',re.U) 21 | words = r.split(text.upper()) 22 | 23 | # тут нужно прописать путь до папки со словарями 24 | morph = pymorphy.get_morph('dicts/converted/ru') 25 | 26 | for word in words: 27 | if word: 28 | print word 29 | info = morph.get_graminfo(word) 30 | for form in info: 31 | pymorphy.utils.pprint(form) 32 | -------------------------------------------------------------------------------- /pymorphy/__init__.py: -------------------------------------------------------------------------------- 1 | from pymorphy.morph import get_morph 2 | from pymorphy.version import * 3 | -------------------------------------------------------------------------------- /pymorphy/_morph.pxd: -------------------------------------------------------------------------------- 1 | import cython 2 | 3 | @cython.locals(l=int, vars=list, i=int) 4 | cpdef list _get_split_variants(unicode word) 5 | 6 | @cython.locals(item=unicode) 7 | cpdef int _array_match(list arr, list filter) 8 | 9 | 10 | cdef class GramForm(object): 11 | cpdef public set form 12 | cpdef public set denied_form 13 | 14 | cpdef GramForm update(self, unicode form_string) 15 | cpdef unicode get_form_string(self) 16 | cpdef int match(self, GramForm gram_form) 17 | 18 | 19 | cdef class Morph(object): 20 | cpdef object data 21 | cpdef int check_prefixes 22 | cpdef int predict_by_prefix 23 | cpdef int predict_by_suffix 24 | cpdef int prediction_max_prefix_len 25 | cpdef int prediction_min_suffix_len 26 | cpdef int handle_EE 27 | 28 | @cython.locals(paradigm=list, gram=list) 29 | cpdef list _get_lemma_graminfo(self, unicode lemma, unicode suffix, unicode require_prefix, unicode method_format_str) 30 | 31 | -------------------------------------------------------------------------------- /pymorphy/backends/__init__.py: -------------------------------------------------------------------------------- 1 | from pymorphy.backends.pickle_source import PickleDataSource 2 | from pymorphy.backends.shelve_source import ShelveDataSource 3 | from pymorphy.backends.mrd_source import MrdDataSource -------------------------------------------------------------------------------- /pymorphy/backends/base.py: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | from __future__ import print_function 3 | from pymorphy.utils import pprint, get_split_variants 4 | 5 | 6 | class DictDataSource(object): 7 | ''' 8 | Absctract base class for dictionary data source. 9 | Subclasses should make class variables (rules, lemmas, prefixes, 10 | gramtab, endings, possible_rule_prefixes) accessible through dict 11 | or list syntax ("duck typing") 12 | 13 | Абстрактный базовый класс для источников данных pymorphy. 14 | У подклассов должны быть свойства rules, lemmas, prefixes, 15 | gramtab, endings, possible_rule_prefixes, к которым можно было бы 16 | обращаться как к словарям, спискам или множествам. 17 | 18 | .. glossary:: 19 | 20 | rules 21 | для каждой парадигмы - список правил в виде словаря 22 | {приставка: [(грам. информация, префикс, порядковый номер)]}:: 23 | 24 | {paradigm_id -> {suffix: [(ancode, prefix, index)]} } 25 | 26 | правила с меньшим порядковым номером считаются более "каноническими" 27 | в пределах одной парадигмы. 28 | 29 | lemmas 30 | для каждой леммы - список номеров парадигм (способов 31 | образования слов), доступных для данной леммы (основы слова):: 32 | 33 | {base -> [paradigm_id]} 34 | 35 | prefixes 36 | фиксированые префиксы:: 37 | 38 | set([prefix]) 39 | 40 | gramtab 41 | грамматическая информация: словарь, ключи которого - индексы грам. 42 | информации (анкоды), значения - кортежи 43 | (часть речи, информация о грам. форме, какая-то непонятная буква):: 44 | 45 | {ancode->(type,info,letter)} 46 | 47 | normal_forms 48 | для каждой парадигмы - наиболее вероятная нормальная форма:: 49 | 50 | {paradigm_id -> (suffix, ancode, prefix)} 51 | 52 | rule_freq 53 | частоты для правил, используется при подготовке словарей:: 54 | 55 | {paradigm_id->freq} 56 | 57 | endings 58 | для каждого возможного 5 буквенного окончания - словарь, в котором 59 | ключи - номера возможных парадигм, а значения - возможные правила 60 | в формате (suffix, ancode, prefix):: 61 | 62 | {word_end->{paradigm_id->tuple(possible_rules)}} 63 | 64 | possible_rule_prefixes 65 | набор всех возможных приставок к леммам:: 66 | 67 | [prefix] 68 | 69 | 70 | ''' 71 | def __init__(self): 72 | self.rules = {} 73 | self.lemmas = {} 74 | self.prefixes = set() 75 | self.endings = {} 76 | self.gramtab = {} 77 | self.possible_rule_prefixes = set() 78 | self.rule_freq = {} 79 | self.normal_forms = {} 80 | self.accents = [] # ударения, не используется 81 | self.logs = [] # логи работы с оригинальной программой от aot, не используется 82 | 83 | def load(self): 84 | """ Загрузить данные """ 85 | raise NotImplementedError 86 | 87 | def convert_and_save(self, data_obj): 88 | """ Взять данные из data_obj (наследник DataDictSource) 89 | и сохранить из в специфичном для класса формате. 90 | """ 91 | raise NotImplementedError 92 | 93 | def calculate_rule_freq(self): 94 | """ 95 | Подсчитать частоту, с которой встречаются различные правила. 96 | Требуется для предсказателя, чтобы выбирать наиболее распространенные 97 | варианты. 98 | """ 99 | for lemma in self.lemmas: 100 | for paradigm_id in self.lemmas[lemma]: 101 | self.rule_freq[paradigm_id] = self.rule_freq.get(paradigm_id, 0)+1 102 | 103 | #@profile 104 | def analyze(self, word): 105 | """ 106 | Возвращает (lemma, paradigm_id, rule) со всеми вариантами разбора слова. 107 | 108 | lemma - с какой леммой слово разобрали; 109 | paradigm_id - номер парадигмы; 110 | rule - подходящее правило в парадигме. 111 | 112 | Подклассы, использующие специализированные структуры для хранения 113 | словарей, могут реализовать этот метод эффективнее. 114 | """ 115 | 116 | rules = self.rules 117 | lemmas = self.lemmas 118 | 119 | # Основная проверка по словарю: разбиваем слово на 2 части, 120 | # считаем одну из них леммой, другую окончанием, пробуем найти 121 | # лемму в словаре; если нашли, то пробуем найти вариант разбора 122 | # с подходящим окончанием. 123 | for lemma, suffix in get_split_variants(word): 124 | if lemma in lemmas: 125 | for paradigm_id in lemmas[lemma]: 126 | paradigm = rules[paradigm_id] 127 | if suffix in paradigm: 128 | for rule in paradigm[suffix]: 129 | yield lemma, paradigm_id, (suffix, rule[0], rule[1]) 130 | 131 | # Вариант с пустой леммой (например, ЧЕЛОВЕК - ЛЮДИ). 132 | # У таких слов в словарях основа записана как "#". 133 | for paradigm_id in lemmas['#']: 134 | paradigm = rules[paradigm_id] 135 | if word in paradigm: 136 | for rule in paradigm[word]: 137 | yield '', paradigm_id, (word, rule[0], rule[1]) 138 | 139 | 140 | def _check_self(self): 141 | """ Проверить словарь на корректность """ 142 | paradigm_ids = self.rules.keys() 143 | 144 | print('checking paradigms...') 145 | # правила 146 | for paradigm_id, paradigm_rules in self.rules.iteritems(): 147 | if not paradigm_rules: 148 | print (' no rules for paradigm %d' % paradigm_id) 149 | print ('%d paradigms were checked' % len(paradigm_ids)) 150 | 151 | print ('checking lemmas...') 152 | # леммы 153 | for base, paradigms in self.lemmas.iteritems(): 154 | for id in paradigms: 155 | if id not in paradigm_ids: 156 | print (' invalid paradigm %d for lemma %s' % (id, base)) 157 | print ('%d lemmas were checked' % len(self.lemmas.keys())) 158 | 159 | def _check_other(self, other): 160 | """ Сравнить свои данные с данными из другого источника, считая 161 | самого себя непогрешимым. """ 162 | 163 | print ("checking other's paradigms...") 164 | errors = 0 165 | for paradigm_id, rules in self.rules.iteritems(): 166 | if paradigm_id not in other.rules: 167 | print (" paradigm %d doesn't exist" % paradigm_id) 168 | errors += 1 169 | continue 170 | 171 | # FIXME: проверить значения 172 | # # приводим все к tuple 173 | # other_rules = [tuple(r) for r in other.rules[paradigm_id]] 174 | # if rules != other_rules: 175 | # print (' paradigm %s is incorrect:' % paradigm_id) 176 | # pprint(rules) 177 | # print ('!=') 178 | # pprint(other_rules) 179 | # print ('--------------------') 180 | # errors += 1 181 | 182 | if errors: 183 | print ('%d errors found.' % errors) 184 | 185 | errors = 0 186 | print ("checking other's lemmas...") 187 | for lemma, paradigms in self.lemmas.iteritems(): 188 | if lemma not in other.lemmas: 189 | print (" lemma %s doesn't exist" % lemma) 190 | errors += 1 191 | continue 192 | other_paradigms = other.lemmas[lemma] 193 | if paradigms != other_paradigms: 194 | print (' lemma %s is incorrect: %s != %s' % (lemma, other_paradigms, paradigms)) 195 | errors += 1 196 | if errors: 197 | print ('%d errors found.' % errors) 198 | 199 | -------------------------------------------------------------------------------- /pymorphy/backends/mrd_source.py: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | from __future__ import unicode_literals 3 | import codecs 4 | from pymorphy.backends.base import DictDataSource 5 | from pymorphy.constants import PRODUCTIVE_CLASSES 6 | 7 | 8 | class MrdDataSource(DictDataSource): 9 | """ 10 | Источник данных для морфологического анализатора pymorphy, 11 | берущий информацию из оригинальных mrd-файлов (в которых кодировка 12 | была изменена с 1251 на utf-8). Используется для конвертации 13 | оригинальных данных в простые для обработки ShelveDict или PickledDict. 14 | """ 15 | 16 | def __init__(self, dict_name, gramtab_name, strip_EE=True): 17 | super(MrdDataSource, self).__init__() 18 | self.dict_name = dict_name 19 | self.gramtab_name = gramtab_name 20 | self.strip_EE = strip_EE 21 | 22 | def load(self): 23 | self._load(self.dict_name, self.gramtab_name) 24 | self.calculate_rule_freq() 25 | self._calculate_endings() 26 | self._cleanup_endings() 27 | 28 | #----------- protected methods ------------- 29 | 30 | def _section_lines(self, file): 31 | """ Прочитать все строки в секции mrd-файла, заменяя Ё на Е, 32 | если установлен параметр strip_EE 33 | """ 34 | lines_count = int(file.readline()) 35 | for i in xrange(0, lines_count): 36 | if self.strip_EE: 37 | yield file.readline().replace('Ё','Е') 38 | else: 39 | yield file.readline() 40 | 41 | def _pass_lines(self, file): 42 | """ Пропустить секцию """ 43 | for line in self._section_lines(file): 44 | pass 45 | 46 | def _load_rules(self, file): 47 | """ Загрузить все парадигмы слов""" 48 | for paradigm_id, line in enumerate(self._section_lines(file)): 49 | line_rules = line.strip().split('%') 50 | 51 | index = 0 # не enumerate, чтоб не считать пустые строки 52 | for rule in line_rules: 53 | if not rule: 54 | continue 55 | 56 | parts = rule.split('*') 57 | if len(parts)==2: 58 | parts.append('') 59 | 60 | suffix, ancode, prefix = parts 61 | ancode = ancode[:2] 62 | 63 | if paradigm_id not in self.rules: 64 | self.rules[paradigm_id] = {} 65 | # первое встреченное правило считаем за нормальную форму 66 | self.normal_forms[paradigm_id] = parts 67 | paradigm = self.rules[paradigm_id] 68 | 69 | if suffix not in paradigm: 70 | paradigm[suffix] = [] 71 | 72 | paradigm[suffix].append((ancode, prefix, index)) 73 | 74 | if prefix: 75 | self.possible_rule_prefixes.add(prefix) 76 | 77 | index += 1 78 | 79 | 80 | def _load_lemmas(self, file): 81 | """ Загрузить текущую секцию как секцию с леммами """ 82 | for line in self._section_lines(file): 83 | record = line.split() 84 | base, paradigm_id = record[0], record[1] 85 | 86 | # Информацию об ударениях, пользовательских сессиях, 87 | # общий для всех парадигм анкод и наборы префиксов мы тут не 88 | # учитываем. 89 | # Сессии - специфичная для aot-редактора информация, 90 | # анкод можно получить из парадигмы, префикс все равно 91 | # дублирует "префиксы" self.prefixes (?). 92 | # accent_model_no, session_no, type_ancode, prefix_set_no = record[2:] 93 | 94 | if base not in self.lemmas: 95 | self.lemmas[base] = [] 96 | 97 | self.rule_freq[paradigm_id] = self.rule_freq.get(paradigm_id, 0) + 1 98 | 99 | # FIXME: т.к. мы отбрасываем анкод, у нас тут могут быть 100 | # дубликаты парадигм (и будут, для ДУМА, например). 101 | self.lemmas[base].append(int(paradigm_id)) 102 | 103 | def _load_accents(self, file): 104 | return self._pass_lines(file) 105 | 106 | def _load_logs(self, file): 107 | """ Загрузить текущую секцию как секцию с логами (бесполезная штука) """ 108 | for line in self._section_lines(file): 109 | self.logs.append(line.strip()) 110 | 111 | def _load_prefixes(self, file): 112 | """ Загрузить текущую секцию как секцию с префиксами """ 113 | for line in self._section_lines(file): 114 | self.prefixes.add(line.strip()) 115 | 116 | def _load_gramtab(self, file): 117 | """ Загрузить грамматическую информацию из файла """ 118 | for line in file: 119 | line=line.strip() 120 | if line.startswith('//') or line == '': 121 | continue 122 | g = line.split() 123 | if len(g)==3: 124 | g.append('') 125 | ancode, letter, type, info = g[0:4] 126 | self.gramtab[ancode] = (type, info, letter,) 127 | 128 | def _load(self, filename, gramfile): 129 | with codecs.open(filename, 'r', 'utf8') as dict_file: 130 | self._load_rules(dict_file) 131 | self._load_accents(dict_file) 132 | self._load_logs(dict_file) 133 | self._load_prefixes(dict_file) 134 | self._load_lemmas(dict_file) 135 | 136 | with codecs.open(gramfile, 'r', 'utf8') as gram_file: 137 | self._load_gramtab(gram_file) 138 | 139 | def _calculate_endings(self): 140 | """ 141 | Подсчитать все возможные 5-буквенные окончания слов. 142 | Перебирает все возможные формы всех слов по словарю, смотрит окончание 143 | и добавляет его в словарь. 144 | """ 145 | 146 | # перебираем все слова 147 | for lemma in self.lemmas: 148 | 149 | # берем все возможные парадигмы 150 | for paradigm_id in self.lemmas[lemma]: 151 | paradigm = self.rules[paradigm_id] 152 | 153 | for suffix in paradigm: 154 | for ancode, prefix, index in paradigm[suffix]: 155 | # формируем слово 156 | word = ''.join((prefix, lemma, suffix)) 157 | 158 | # добавляем окончания и номера правил их получения в словарь 159 | for i in 1,2,3,4,5: 160 | word_end = word[-i:] 161 | if word_end: 162 | if word_end not in self.endings: 163 | self.endings[word_end] = {} 164 | ending = self.endings[word_end] 165 | 166 | if paradigm_id not in ending: 167 | ending[paradigm_id]=set() 168 | 169 | ending[paradigm_id].add((suffix, ancode, prefix)) 170 | 171 | 172 | def _cleanup_endings(self): 173 | """ 174 | Очистка правил в словаре возможных окончаний. Правил получается много, 175 | оставляем только те, которые относятся к продуктивным частям речи + 176 | для каждого окончания оставляем только по 1 самому популярному правилу 177 | на каждую часть речи. 178 | """ 179 | for word_end in self.endings: 180 | ending_info = self.endings[word_end] 181 | result_info = {} 182 | best_paradigms = {} 183 | 184 | for paradigm_id in ending_info: 185 | 186 | base_suffix, base_ancode, base_prefix = self.normal_forms[paradigm_id] 187 | base_gram = self.gramtab[base_ancode] 188 | word_class = base_gram[0] 189 | 190 | if word_class not in PRODUCTIVE_CLASSES: 191 | continue 192 | 193 | if word_class not in best_paradigms: 194 | best_paradigms[word_class] = paradigm_id 195 | else: 196 | new_freq = self.rule_freq[paradigm_id] 197 | old_freq = self.rule_freq[best_paradigms[word_class]] 198 | if new_freq > old_freq: 199 | best_paradigms[word_class] = paradigm_id 200 | 201 | for word_class in best_paradigms: 202 | paradigm_id = best_paradigms[word_class] 203 | # приводим к tuple, т.к. set плохо сериализуется 204 | result_info[paradigm_id] = tuple(ending_info[paradigm_id]) 205 | self.endings[word_end] = result_info 206 | 207 | @staticmethod 208 | def setup_psyco(): 209 | """ Оптимизировать узкие места в MrdDataSource с помощью psyco """ 210 | try: 211 | import psyco 212 | psyco.bind(MrdDataSource._calculate_endings) 213 | psyco.bind(MrdDataSource._load_lemmas) 214 | psyco.bind(MrdDataSource._cleanup_endings) 215 | psyco.bind(MrdDataSource._section_lines) 216 | psyco.bind(DictDataSource.calculate_rule_freq) 217 | except ImportError: 218 | pass 219 | -------------------------------------------------------------------------------- /pymorphy/backends/pickle_source.py: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | try: 3 | import cPickle as pickle 4 | except ImportError: 5 | import pickle 6 | 7 | from pymorphy.backends.base import DictDataSource 8 | 9 | class PickleDataSource(DictDataSource): 10 | """ 11 | Источник данных для морфологического анализатора pymorphy, 12 | берущий информацию из файлов, куда с помощью pickle были 13 | сохранены данные. Самый быстрый, но ест уйму памяти (> 100 MB). 14 | """ 15 | 16 | def __init__(self, file): 17 | self.file = file 18 | super(PickleDataSource, self).__init__() 19 | 20 | def load(self): 21 | with open(self.file,'rb') as pickle_file: 22 | p = pickle.Unpickler(pickle_file) 23 | self.lemmas = p.load() 24 | self.rules = p.load() 25 | self.gramtab = p.load() 26 | self.prefixes = p.load() 27 | self.possible_rule_prefixes = p.load() 28 | self.endings = p.load() 29 | self.normal_forms = p.load() 30 | self.rule_freq = p.load or {} 31 | 32 | def convert_and_save(self, data_obj): 33 | with open(self.file,'wb') as pickle_file: 34 | p = pickle.Pickler(pickle_file, pickle.HIGHEST_PROTOCOL) 35 | p.dump(data_obj.lemmas) 36 | p.dump(data_obj.rules) 37 | p.dump(data_obj.gramtab) 38 | p.dump(data_obj.prefixes) 39 | p.dump(data_obj.possible_rule_prefixes) 40 | p.dump(data_obj.endings) 41 | p.dump(data_obj.normal_forms) 42 | if data_obj.rule_freq: 43 | p.dump(data_obj.rule_freq) 44 | 45 | def __str__(self): 46 | return 'PickleDataSource (%s)' % self.file -------------------------------------------------------------------------------- /pymorphy/backends/shelve_source/__init__.py: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | from __future__ import absolute_import 3 | import os 4 | 5 | from pymorphy.backends.base import DictDataSource 6 | from .shelf_with_hooks import ShelfWithHooks 7 | 8 | 9 | class ShelveDataSource(DictDataSource): 10 | """ 11 | Источник данных для морфологического анализатора pymorphy, 12 | берущий информацию из key-value базы данных, используя модифицированный 13 | интерфейс shelve из стандартной библиотеки. Позволяет не держать все 14 | данные в памяти и в то же время обеспечивает достаточно быструю скорость 15 | работы. 16 | 17 | Грамматическая информация и префиксы загружаются в память сразу. 18 | """ 19 | 20 | def __init__(self, path='', db_type=None, cached=True): 21 | self.path = path 22 | self.db_type = db_type or 'sqlite' 23 | self.cached = cached 24 | 25 | super(ShelveDataSource, self).__init__() 26 | 27 | def load(self): 28 | self.lemmas = self._get_shelf('lemmas', 'r') 29 | self.rules = self._get_shelf('rules', 'r', 'int') 30 | self.endings = self._get_shelf('endings', 'r') 31 | self.normal_forms = self._get_shelf('normal_forms', 'r', 'int') 32 | 33 | misc = self._get_shelf('misc', 'r') 34 | self.gramtab = misc['gramtab'] 35 | 36 | self.prefixes = set(misc['prefixes']) 37 | self.possible_rule_prefixes = set(misc['possible_rule_prefixes']) 38 | 39 | def convert_and_save(self, data_obj): 40 | lemma_shelve = self._get_shelf('lemmas', 'c') 41 | rules_shelve = self._get_shelf('rules', 'c', 'int') 42 | endings_shelve = self._get_shelf('endings', 'c') 43 | normal_forms_shelve = self._get_shelf('normal_forms', 'c', 'int') 44 | 45 | for lemma in data_obj.lemmas: 46 | lemma_shelve[lemma] = data_obj.lemmas[lemma] 47 | 48 | for rule in data_obj.rules: 49 | rules_shelve[rule] = data_obj.rules[rule] 50 | 51 | for end in data_obj.endings: 52 | endings_shelve[end] = data_obj.endings[end] 53 | 54 | for rule in data_obj.normal_forms: 55 | normal_forms_shelve[rule] = data_obj.normal_forms[rule] 56 | 57 | misc_shelve = self._get_shelf('misc', 'c') 58 | misc_shelve['gramtab'] = data_obj.gramtab 59 | misc_shelve['prefixes'] = list(data_obj.prefixes) 60 | misc_shelve['possible_rule_prefixes'] = list(data_obj.possible_rule_prefixes) 61 | 62 | if data_obj.rule_freq: 63 | freq_shelve = self._get_shelf('freq', 'c', 'int') 64 | for (rule, freq,) in data_obj.rule_freq.items(): 65 | freq_shelve[int(rule)] = freq 66 | freq_shelve.close() 67 | 68 | lemma_shelve.close() 69 | misc_shelve.close() 70 | rules_shelve.close() 71 | endings_shelve.close() 72 | normal_forms_shelve.close() 73 | 74 | 75 | def _path(self, name): 76 | ext = self.db_type 77 | if 'cdb' in self.db_type: 78 | ext = 'cdb' 79 | return os.path.join(self.path, name+'.'+ext) 80 | 81 | def _get_shelf_class(self): 82 | 83 | def python_cdb(): 84 | from .cdb_shelve import CdbShelf 85 | return CdbShelf 86 | 87 | def tinycdb(): 88 | from .tinycdb_shelve import TinycdbShelf 89 | return TinycdbShelf 90 | 91 | def cdblib(): 92 | from .cdblib_shelve import CdblibShelf 93 | return CdblibShelf 94 | 95 | if self.db_type == 'cdb': 96 | try: 97 | return python_cdb() 98 | except ImportError: 99 | try: 100 | return tinycdb() 101 | except ImportError: 102 | return cdblib() 103 | 104 | elif self.db_type == 'tinycdb': 105 | return tinycdb() 106 | 107 | elif self.db_type == 'python-cdb': 108 | return python_cdb() 109 | 110 | elif self.db_type == 'cdblib': 111 | return cdblib() 112 | 113 | elif self.db_type == 'tch': 114 | from .pytc_shelve import PytcHashShelf 115 | return PytcHashShelf 116 | 117 | elif self.db_type == 'tcb': 118 | from .pytc_shelve import PytcBtreeShelf 119 | return PytcBtreeShelf 120 | 121 | elif self.db_type == 'sqlite': 122 | from .sqlite_shelve import SqliteShelf 123 | return SqliteShelf 124 | 125 | elif self.db_type == 'shelve': 126 | return ShelfWithHooks 127 | 128 | raise Exception('Unsupported backend: %s' % self.db_type) 129 | 130 | def _get_shelf(self, filename, *args, **kwargs): 131 | path = self._path(filename) 132 | return self._get_shelf_class()(path, cached=self.cached, *args, **kwargs) 133 | 134 | def _check_self(self): 135 | raise NotImplementedError() 136 | 137 | def _check_other(self, data_source): 138 | raise NotImplementedError() 139 | 140 | def __str__(self): 141 | return 'DataSource [%s] (%s)' % (self.db_type, self.path) 142 | -------------------------------------------------------------------------------- /pymorphy/backends/shelve_source/cdb_shelve.py: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | import cdb 3 | from shelf_with_hooks import ShelfWithHooks 4 | from shelve import Shelf 5 | 6 | class CdbWriteDict(object): 7 | 8 | def __init__(self, filename): 9 | self.db = cdb.cdbmake(filename.encode('utf8'), filename+'.tmp') 10 | 11 | def __setitem__(self, key, value): 12 | self.db.add(key, value) 13 | 14 | def close(self): 15 | return self.db.finish() 16 | 17 | 18 | class CdbReadDict(object): 19 | 20 | def __init__(self, filename): 21 | self.db = cdb.init(filename.encode('utf8')) 22 | 23 | def __getitem__(self, key): 24 | return self.db[key] 25 | 26 | def has_key(self, key): 27 | return self.db.has_key(key) 28 | 29 | def close(self): 30 | pass 31 | 32 | 33 | class CdbShelf(ShelfWithHooks): 34 | 35 | def __init__(self, filename, flag, key_type='str', dump_method=None, 36 | cached=True, writeback=False): 37 | if flag=='r': 38 | Shelf.__init__(self, CdbReadDict(filename), -1, writeback) 39 | elif flag=='c': 40 | Shelf.__init__(self, CdbWriteDict(filename), -1, writeback) 41 | self._setup_methods(cached, key_type, dump_method) 42 | 43 | def close(self): 44 | self.dict.close() 45 | -------------------------------------------------------------------------------- /pymorphy/backends/shelve_source/cdblib_shelve.py: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | from cdblib import CDB, CDBWriter 3 | from shelf_with_hooks import ShelfWithHooks 4 | from shelve import Shelf 5 | 6 | class CDBReader(CDB): 7 | def has_key(self, key): 8 | return self.__contains__(key) 9 | 10 | 11 | class CdblibShelf(ShelfWithHooks): 12 | 13 | def __init__(self, filename, flag, key_type='str', dump_method=None, 14 | cached=True, writeback=False): 15 | if flag=='r': 16 | Shelf.__init__(self, CDBReader(filename), -1, writeback) 17 | elif flag=='c': 18 | Shelf.__init__(self, CDBWriter(filename), -1, writeback) 19 | self._setup_methods(cached, key_type, dump_method) 20 | 21 | def close(self): 22 | self.dict.close() 23 | -------------------------------------------------------------------------------- /pymorphy/backends/shelve_source/pytc_shelve.py: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | import warnings 3 | from shelve import Shelf 4 | import pytc 5 | from shelf_with_hooks import ShelfWithHooks 6 | 7 | warnings.warn("Tokyo Cabinet support is deprecated and will be removed soon. Please consider " 8 | "switching to tinycdb: it is easier to install, faster and uses less memory", DeprecationWarning) 9 | 10 | 11 | class PytcHashShelf(ShelfWithHooks): 12 | 13 | DB_CLASS = pytc.HDB 14 | 15 | def __init__(self, filename, flag, key_type='str', dump_method=None, 16 | cached=True, writeback=False): 17 | 18 | db = self.DB_CLASS() 19 | if flag == 'r': 20 | flags = pytc.BDBOREADER 21 | elif flag == 'c': 22 | flags = pytc.BDBOWRITER | pytc.BDBOREADER | pytc.BDBOCREAT 23 | else: 24 | raise NotImplementedError 25 | 26 | db.open(filename, flags) 27 | Shelf.__init__(self, db, -1, writeback) 28 | self._setup_methods(cached, key_type, dump_method) 29 | 30 | def __delitem__(self, key): 31 | pass 32 | 33 | def __del__(self): 34 | self.close() 35 | 36 | def close(self): 37 | self.dict.close() 38 | 39 | 40 | class PytcBtreeShelf(PytcHashShelf): 41 | DB_CLASS = pytc.BDB 42 | -------------------------------------------------------------------------------- /pymorphy/backends/shelve_source/shelf_with_hooks.py: -------------------------------------------------------------------------------- 1 | #coding: utf8 2 | from shelve import DbfilenameShelf 3 | from struct import pack, unpack 4 | import marshal 5 | try: 6 | import simplejson as json 7 | except ImportError: 8 | import json 9 | 10 | from pymorphy.py3k import text_type, binary_type 11 | 12 | def _to_utf8(s): 13 | if isinstance(s, binary_type): 14 | return s 15 | return s.encode('utf8') 16 | 17 | 18 | def json_dumps(value): 19 | return json.dumps(value, ensure_ascii=False).encode('utf8') 20 | 21 | class ShelfWithHooks(DbfilenameShelf): 22 | ''' Shelf class with key and value transform hooks. ''' 23 | 24 | DUMP_METHODS = { 25 | 'marshal': { 26 | 'loads': marshal.loads, 27 | 'dumps': marshal.dumps 28 | }, 29 | 'json': { 30 | 'loads': json.loads, 31 | 'dumps': json_dumps 32 | }, 33 | } 34 | 35 | KEY_TRANSFORM_METHODS = { 36 | 'unicode': { 37 | 'encode': lambda key: text_type(key),#.encode('utf8'), 38 | 'decode': lambda key: text_type(key),#, 'utf8'), 39 | }, 40 | 'int': { 41 | 'encode': lambda key: pack("H", int(key)), 42 | 'decode': lambda key: unpack('H', key), 43 | }, 44 | 'str': { 45 | 'encode': _to_utf8, 46 | 'decode': lambda key: key, 47 | } 48 | } 49 | 50 | DEFAULT_DUMP_METHOD = 'json' 51 | 52 | def __init__(self, filename, flag, key_type='str', dump_method=None, 53 | cached=True, writeback=False): 54 | DbfilenameShelf.__init__(self, filename, flag, -1, writeback) 55 | cached = (flag is 'r') and cached 56 | self._setup_methods(cached, key_type, dump_method) 57 | 58 | 59 | def _setup_methods(self, cached, key_type, dump_method=None): 60 | dump_method = dump_method or self.DEFAULT_DUMP_METHOD 61 | 62 | if cached: 63 | self.__getitem__ = self._getitem__cached 64 | self.__contains__ = self._contains__cached 65 | 66 | self._encode_key = self.KEY_TRANSFORM_METHODS[key_type]['encode'] 67 | self._decode_key = self.KEY_TRANSFORM_METHODS[key_type]['decode'] 68 | 69 | self._dumps_value = self.DUMP_METHODS[dump_method]['dumps'] 70 | self._loads_value = self.DUMP_METHODS[dump_method]['loads'] 71 | 72 | 73 | def __setitem__(self, key, value): 74 | self.dict[self._encode_key(key)] = self._dumps_value(value) 75 | 76 | def __contains__(self, key): 77 | return self.dict.has_key(self._encode_key(key)) 78 | 79 | def __getitem__(self, key): 80 | return self._loads_value(self.dict[self._encode_key(key)]) 81 | 82 | def _contains__cached(self, key): 83 | if key in self.cache: 84 | return True 85 | key = self._encode_key(key) 86 | return self.dict.has_key(key) 87 | 88 | def _getitem__cached(self, key): 89 | if key in self.cache: 90 | return self.cache[key] 91 | value = self._loads_value(self.dict[self._encode_key(key)]) 92 | self.cache[key] = value 93 | return value 94 | 95 | # а эти методы нам не нужны 96 | def has_key(self, key): 97 | raise NotImplementedError 98 | def keys(self): 99 | raise NotImplementedError 100 | def get(self, key, default=None): 101 | raise NotImplementedError 102 | def __delitem__(self, key): 103 | raise NotImplementedError 104 | 105 | def close(self): 106 | pass 107 | 108 | -------------------------------------------------------------------------------- /pymorphy/backends/shelve_source/sqlite_shelve.py: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | from __future__ import absolute_import 3 | import sqlite3 4 | import threading 5 | from shelve import Shelf 6 | from .shelf_with_hooks import ShelfWithHooks 7 | 8 | 9 | class SqliteDict(object): 10 | "A dictionary that stores its data in a table in sqlite3 database" 11 | 12 | def __init__(self, filename=None, connection=None, table='shelf'): 13 | 14 | self._lock = threading.RLock() 15 | 16 | if connection is not None: 17 | self.conn = connection 18 | else: 19 | self.conn = sqlite3.connect(filename, check_same_thread = False) 20 | 21 | self.conn.text_factory = str 22 | 23 | self._table = table 24 | 25 | self.MAKE_SHELF = 'CREATE TABLE IF NOT EXISTS %s (key TEXT PRIMARY KEY, value TEXT NOT NULL)' % self._table 26 | self.GET_ITEM = 'SELECT value FROM %s WHERE key = ?' % self._table 27 | self.ADD_ITEM = 'REPLACE INTO %s (key, value) VALUES (?,?)' % self._table 28 | self.CLEAR_ALL = 'DELETE FROM %s; VACUUM;' % self._table 29 | self.HAS_ITEM = 'SELECT 1 FROM %s WHERE key = ?' % self._table 30 | 31 | with self._lock: 32 | self.conn.execute(self.MAKE_SHELF) 33 | self.conn.commit() 34 | 35 | def has_key(self, key): 36 | with self._lock: 37 | return self.conn.execute(self.HAS_ITEM, (key,)).fetchone() is not None 38 | 39 | def __contains__(self, key): 40 | return self.has_key(key) 41 | 42 | def __getitem__(self, key): 43 | with self._lock: 44 | item = self.conn.execute(self.GET_ITEM, (key,)).fetchone() 45 | 46 | if item is None: 47 | raise KeyError(key) 48 | return item[0] 49 | 50 | def __setitem__(self, key, value): 51 | with self._lock: 52 | self.conn.execute(self.ADD_ITEM, (key, value)) #sqlite3.Binary(value))) 53 | # self.conn.commit() 54 | 55 | def clear(self): 56 | with self._lock: 57 | self.conn.executescript(self.CLEAR_ALL) 58 | self.conn.commit() 59 | 60 | def sync(self): 61 | with self._lock: 62 | if self.conn is not None: 63 | self.conn.commit() 64 | 65 | def close(self): 66 | if self.conn is None: 67 | return 68 | 69 | try: 70 | with self._lock: 71 | self.conn.commit() 72 | self.conn.close() 73 | self.conn = None 74 | except (sqlite3.ProgrammingError, sqlite3.OperationalError): 75 | pass 76 | 77 | def __del__(self): 78 | self.close() 79 | 80 | 81 | class SqliteShelf(ShelfWithHooks): 82 | 83 | def __init__(self, filename=None, flag='', key_type='unicode', 84 | dump_method=None, cached=True, 85 | connection=None, table='shelf',): 86 | Shelf.__init__(self, SqliteDict(filename, connection, table)) 87 | 88 | # 'int' type packs integer key to 2-byte sequence and 89 | # sqlite doesn't support binary data without extra efforts 90 | if key_type == 'int': 91 | key_type = 'unicode' 92 | 93 | self._setup_methods(cached, key_type, dump_method) 94 | 95 | def close(self): 96 | self.dict.close() 97 | -------------------------------------------------------------------------------- /pymorphy/backends/shelve_source/tinycdb_shelve.py: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | import tinycdb 3 | from shelf_with_hooks import ShelfWithHooks 4 | from shelve import Shelf 5 | 6 | class TinycdbShelf(ShelfWithHooks): 7 | 8 | def __init__(self, filename, flag, key_type='str', dump_method=None, 9 | cached=True, writeback=False): 10 | if flag=='r': 11 | Shelf.__init__(self, tinycdb.read(filename), -1, writeback) 12 | elif flag=='c': 13 | Shelf.__init__(self, tinycdb.create(filename), -1, writeback) 14 | self._setup_methods(cached, key_type, dump_method) 15 | 16 | def close(self): 17 | self.dict.close() 18 | -------------------------------------------------------------------------------- /pymorphy/constants.py: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | from __future__ import unicode_literals 3 | 4 | NOUNS = ('NOUN', 'С',) 5 | PRONOUNS = ('PN', 'МС',) 6 | PRONOUNS_ADJ = ('PN_ADJ', 'МС-П',) 7 | VERBS = ('Г', 'VERB', 'ИНФИНИТИВ',) 8 | ADJECTIVE = ('ADJECTIVE', 'П',) 9 | 10 | PRODUCTIVE_CLASSES = NOUNS + VERBS + ADJECTIVE + ('Н',) 11 | 12 | #род 13 | RU_GENDERS_STANDARD = { 14 | 'мр': 'm', 15 | 'жр': 'f', 16 | 'ср': 'n', 17 | 'мр-жр': '', #FIXME: ? 18 | } 19 | 20 | # падежи 21 | RU_CASES_STANDARD = { 22 | 'им': 'nom', 23 | 'рд': 'gen', 24 | 'дт': 'dat', 25 | 'вн': 'acc', 26 | 'тв': 'ins', 27 | 'пр': 'loc', 28 | 'зв': 'voc', 29 | } 30 | 31 | # числа 32 | RU_NUMBERS_STANDARD = {'ед': 'sg', 'мн': 'pl'} 33 | 34 | # лица 35 | RU_PERSONS_STANDARD = {'1л': '1p', '2л': '2p', '3л': '3p'} 36 | 37 | # времена 38 | RU_TENSES_STANDARD = { 39 | 'нст': 'pres', 40 | 'прш': 'past', 41 | 'буд': 'pres', #FIXME: ? 42 | } 43 | 44 | # залоги 45 | RU_VOICES_STANDARD = {'дст': 'act', 'стр': 'pass'} 46 | 47 | # части речи 48 | 49 | RU_CLASSES_STANDARD = { 50 | 'С': 'S', 51 | 'П': 'A', 52 | 'МС': '-', 53 | 'Г' : 'V', 54 | 'ПРИЧАСТИЕ' : 'V', 55 | 'ДЕЕПРИЧАСТИЕ' : 'V', 56 | 'ИНФИНИТИВ': 'V', 57 | 'МС-ПРЕДК': '-', 58 | 'МС-П': '-', 59 | 'ЧИСЛ': '-', 60 | 'ЧИСЛ-П': '-', 61 | 'Н': 'ADV', 62 | 'ПРЕДК': '-', 63 | 'ПРЕДЛ': 'PR', 64 | 'СОЮЗ': 'CONJ', 65 | 'МЕЖД': 'ADV', 66 | 'ЧАСТ': 'ADV', 67 | 'ВВОДН': 'ADV', 68 | 'КР_ПРИЛ': 'A', 69 | 'КР_ПРИЧАСТИЕ': 'V', #FIXME: ? 70 | 'ПОСЛ': '-', 71 | 'ФРАЗ': '-', 72 | } 73 | 74 | # старые обозначения 75 | RU_GENDERS = RU_GENDERS_STANDARD.keys() 76 | RU_CASES = RU_CASES_STANDARD.keys() 77 | RU_NUMBERS = RU_NUMBERS_STANDARD.keys() 78 | RU_PERSONS = RU_PERSONS_STANDARD.keys() 79 | RU_TENSES = RU_TENSES_STANDARD.keys() 80 | RU_VOICES = RU_VOICES_STANDARD.keys() 81 | 82 | RU_ANIMACY = ['од', 'но'] # для этого стандартных обозначений нет 83 | 84 | RU_GRAMINFO_STANDARD = dict(list(RU_GENDERS_STANDARD.items()) + list(RU_CASES_STANDARD.items()) +\ 85 | list(RU_NUMBERS_STANDARD.items()) + list(RU_PERSONS_STANDARD.items()) + \ 86 | list(RU_TENSES_STANDARD.items()) + list(RU_VOICES_STANDARD.items())) 87 | 88 | # данные для упрощения преобразования причастий, деепричастий и инфинитивов в глагол 89 | RU_GRAMINFO_STANDARD.update({'partcp': 'partcp', 'ger': 'ger', 'inf': 'inf'}) 90 | 91 | # прочие преобразования 92 | RU_GRAMINFO_STANDARD.update({'сравн': 'comp', 'прев': 'supr', 'пвл': 'imper'}) 93 | 94 | # таблицы нормальных форм для всех частей речи: характерный набор 95 | # грамматическиех атрибутов + часть речи, в которую идет нормализация 96 | # + стандартное представление 97 | #NORMAL_FORMS_RU = { 98 | # 'С': ('им,ед', 'С', 'S'), 99 | # 'П': ('им,ед,!прев,!сравн', 'П', 'A'), 100 | # 'МС': ('им,ед', 'МС', '-'), 101 | # 'Г' : ('', 'ИНФИНИТИВ', 'V'), 102 | # 'ПРИЧАСТИЕ' : ('', 'ИНФИНИТИВ', 'V'), 103 | # 'ДЕЕПРИЧАСТИЕ' : ('', 'ИНФИНИТИВ', 'V'), 104 | # 'ИНФИНИТИВ': ('', 'ИНФИНИТИВ', 'V'), 105 | # 'МС-ПРЕДК': ('', 'МС-ПРЕДК', '-'), 106 | # 'МС-П': ('им,ед', 'МС-П', '-'), 107 | # 'ЧИСЛ': ('им', 'ЧИСЛ', '-'), 108 | # 'ЧИСЛ-П': ('', 'ЧИСЛ-П', '-'), 109 | # 'Н': ('', 'Н', 'ADV'), 110 | # 'ПРЕДК': ('', 'ПРЕДК', '-'), 111 | # 'ПРЕДЛ': ('', 'ПРЕДЛ', 'PR'), 112 | # 'СОЮЗ': ('', 'СОЮЗ', 'CONJ'), 113 | # 'МЕЖД': ('', 'МЕЖД', 'ADV'), 114 | # 'ЧАСТ': ('', 'ЧАСТ', 'ADV'), 115 | # 'ВВОДН': ('', 'ВВОДН', 'ADV'), 116 | # 'КР_ПРИЛ': ('ед', 'П', 'A'), 117 | # 'КР_ПРИЧАСТИЕ': ('', 'КР_ПРИЧАСТИЕ','V'), #FIXME: ? 118 | # 'ПОСЛ': ('', 'ПОСЛ', '-'), 119 | # 'ФРАЗ': ('', 'ФРАЗ', '-'), 120 | #} 121 | # 122 | ## вариант, при котором нормальной формой считается слово в мужском роде 123 | #NORMAL_FORMS_RU_DROP_GENDER = NORMAL_FORMS_RU.copy() 124 | #NORMAL_FORMS_RU_DROP_GENDER.update({ 125 | # 'П': ('им,ед,!прев,!сравн,мр', 'П', 'A'), 126 | # 'КР_ПРИЛ': ('ед,мр', 'П', 'A'), 127 | #}) 128 | 129 | #NORMAL_FORMS_EN = { 130 | # 'ADJECTIVE': ('','ADJECTIVE'), 131 | # 'NUMERAL': ('','NUMERAL'), 132 | # 'ADVERB': ('','ADVERB'), 133 | # 'VERB': ('','VERB'), 134 | # 'MOD': ('','MOD'), 135 | # 'VBE': ('','VBE'), 136 | # 'PN': ('','PN'), 137 | # 'PN_ADJ': ('','PN_ADJ'), 138 | # 'PRON': ('','PRON'), 139 | # 'NOUN': ('','NOUN'), 140 | # 'CONJ': ('','CONJ'), 141 | # 'INT': ('','INT'), 142 | # 'PREP': ('','PREP'), 143 | # 'PART': ('','PART'), 144 | # 'ARTICLE': ('','ARTICLE'), 145 | # 'ORDNUM': ('','ORDNUM'), 146 | # 'POSS': ('','POSS'), 147 | # '*': ('','*'), 148 | #} 149 | # 150 | #NORMAL_FORMS = {} 151 | #NORMAL_FORMS.update(NORMAL_FORMS_RU) 152 | #NORMAL_FORMS.update(NORMAL_FORMS_EN) 153 | # 154 | #NORMAL_FORMS_DROP_GENDER = NORMAL_FORMS_RU_DROP_GENDER.copy() 155 | 156 | #KEEP_GENDER_CLASSES = NOUNS+PRONOUNS+PRONOUNS_ADJ+ADJECTIVE+('КР_ПРИЛ',) -------------------------------------------------------------------------------- /pymorphy/contrib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmike/pymorphy/9a6614449447c6ceee35e6d0dfe36903ebc80c5f/pymorphy/contrib/__init__.py -------------------------------------------------------------------------------- /pymorphy/contrib/lastnames_ru.py: -------------------------------------------------------------------------------- 1 | #-*- coding: UTF-8 2 | from __future__ import unicode_literals 3 | import re 4 | from pymorphy.morph import GramForm 5 | 6 | # Порядок важен: ЯНЦ должно быть перед ЯН для правильного срабатывания. 7 | LASTNAME_PATTERN = re.compile(r'(.*(' 8 | r'ОВ|ИВ|ЕВ' 9 | r'|ИН' 10 | r'|СК|ЦК' 11 | r'|ИЧ' 12 | r'|ЮК|УК' 13 | r'|ИС|ЭС|УС' 14 | r'|ЫХ|ИХ' 15 | r'|ЯНЦ|ЕНЦ|АН|ЯН' 16 | # Фамилии без явного суффикса (-ок, -ия, -иа) сюда включать не надо - decline() попытается угадать лемму. 17 | # Несклоняемые фамилии включать так же не надо - у них нет игнорируемой части после суффикса. 18 | # Фамилии с суффиксами -ых/-их включены сюда т.к. эти окончания образуют множ. форму CASES_OV 19 | r'))', 20 | re.UNICODE | re.VERBOSE) 21 | 22 | # XXX: Й тут нет сознательно? 23 | CONSONANTS = 'БВГДЖЗКЛМНПРСТФХЦЧШЩ' 24 | 25 | # http://www.gramota.ru/spravka/letters/?rub=rubric_482 26 | # http://planeta-imen.narod.ru/slovar-smolenskich-familij/struktura-familij.html 27 | 28 | # 13.1.1, 13.1.3 29 | CASES_OV = { 30 | 'мр': ('', 'А', 'У', 'А', 'ЫМ', 'Е'), 31 | 'жр': ('А', 'ОЙ', 'ОЙ', 'У', 'ОЙ', 'ОЙ'), 32 | } 33 | # 13.1.2 34 | CASES_SK = { 35 | 'мр': ('ИЙ', 'ОГО', 'ОМУ', 'ОГО', 'ИМ', 'ОМ'), 36 | 'жр': ('АЯ', 'ОЙ', 'ОЙ', 'УЮ', 'ОЙ', 'ОЙ'), 37 | } 38 | # Белорусские фамилии на -ич, украинские на -ук, -юк 39 | CASES_CH = { 40 | 'мр': ('', 'А', 'У', 'А', 'ЕМ', 'Е'), 41 | 'жр': ('', '', '', '', '', ''), 42 | } 43 | # Фамилии заканчивающиеся на -ок 44 | CASES_OK = { 45 | 'мр': ('ОК', 'КА', 'КУ', 'КА', 'КОМ', 'КЕ'), 46 | 'жр': ('ОК', 'ОК', 'ОК', 'ОК', 'ОК', 'ОК'), 47 | } 48 | # Фамилии заканчивающиеся на -ец 49 | CASES_EC = { 50 | 'мр': ('ЕЦ', 'ЦА', 'ЦУ', 'ЦА', 'ЦОМ', 'ЦЕ'), 51 | 'жр': ('ЕЦ', 'ЕЦ', 'ЕЦ', 'ЕЦ', 'ЕЦ', 'ЕЦ'), 52 | } 53 | # Литовские, эстонские, часть армянских 54 | CASES_IS = { 55 | 'мр': ('', 'А', 'У', 'А', 'ОМ', 'Е'), 56 | 'жр': ('', '', '', '', '', ''), 57 | } 58 | # 13.1.12 (not really) 59 | CASES_IA = { 60 | 'мр': ('ИЯ', 'ИЮ', 'ИИ', 'ИЮ', 'ИЕЙ', 'ИИ'), 61 | 'жр': ('ИЯ', 'ИЮ', 'ИИ', 'ИЮ', 'ИЕЙ', 'ИИ'), 62 | } 63 | # 13.1.6, 13.1.10 64 | INDECLINABLE_CASES = { 65 | 'мр': ('', '', '', '', '', ''), 66 | 'жр': ('', '', '', '', '', ''), 67 | } 68 | 69 | # Склонение в множественную форму: общие суффиксы для обоих родов 70 | 71 | # Множественная форма для CASES_OV 72 | PLURAL_OV = ('Ы', 'ЫХ', 'ЫМ', 'ЫХ', 'ЫМИ', 'ЫХ') 73 | # для CASES_SK 74 | PLURAL_SK = ('ИЕ', 'ИХ', 'ИМ', 'ИХ', 'ИМИ', 'ИХ') 75 | # для CASES_OK 76 | PLURAL_OK = ('КИ', 'КОВ', 'КАМ', 'КОВ', 'КАМИ', 'КАХ') 77 | # для CASES_EC 78 | PLURAL_EC = ('ЦЫ', 'ЦОВ', 'ЦАМ', 'ЦОВ', 'ЦАМИ', 'ЦАХ') 79 | # для INDECLINABLE_CASES (и фамилий которые не склоняются во множ. числе) 80 | PLURAL_INDECLINABLE_CASES = ('', '', '', '', '', '') 81 | 82 | # Суффикс -> (Склонение единственной формы, Склонение множественной формы) 83 | CASEMAP = { 84 | 'ОВ': (CASES_OV, PLURAL_OV), 85 | 'ИВ': (CASES_OV, PLURAL_OV), 86 | 'ЕВ': (CASES_OV, PLURAL_OV), 87 | 'ИН': (CASES_OV, PLURAL_OV), 88 | 'СК': (CASES_SK, PLURAL_SK), 89 | 'ЦК': (CASES_SK, PLURAL_SK), 90 | 'ИЧ': (CASES_CH, PLURAL_INDECLINABLE_CASES), 91 | 'ЮК': (CASES_CH, PLURAL_INDECLINABLE_CASES), 92 | 'УК': (CASES_CH, PLURAL_INDECLINABLE_CASES), 93 | 'ИС': (CASES_IS, PLURAL_INDECLINABLE_CASES), 94 | 'ЭС': (CASES_IS, PLURAL_INDECLINABLE_CASES), 95 | 'УС': (CASES_IS, PLURAL_INDECLINABLE_CASES), 96 | # Часть армянских фамилий склоняется так же как литовские 97 | 'АН': (CASES_IS, PLURAL_INDECLINABLE_CASES), 98 | 'ЯН': (CASES_IS, PLURAL_INDECLINABLE_CASES), 99 | # Другая часть армянских фамилий склоняется как украинские 100 | # заканчивающиеся на ИЧ 101 | 'УНЦ': (CASES_CH, PLURAL_INDECLINABLE_CASES), 102 | 'ЯНЦ': (CASES_CH, PLURAL_INDECLINABLE_CASES), 103 | 'ЕНЦ': (CASES_CH, PLURAL_INDECLINABLE_CASES), 104 | 'ИЯ': (CASES_IA, PLURAL_INDECLINABLE_CASES), 105 | 'ОК': (CASES_OK, PLURAL_OK), 106 | # Склонение -ец похоже на фамилии с суффиксом -ок 107 | 'ЕЦ': (CASES_EC, PLURAL_EC), 108 | 'ЫХ': (INDECLINABLE_CASES, PLURAL_INDECLINABLE_CASES), 109 | 'ИХ': (INDECLINABLE_CASES, PLURAL_INDECLINABLE_CASES), 110 | 'КО': (INDECLINABLE_CASES, PLURAL_INDECLINABLE_CASES), 111 | 'АГО': (INDECLINABLE_CASES, PLURAL_INDECLINABLE_CASES), 112 | 'ЯГО': (INDECLINABLE_CASES, PLURAL_INDECLINABLE_CASES), 113 | 'ИА': (INDECLINABLE_CASES, PLURAL_INDECLINABLE_CASES), 114 | 'ХНО': (INDECLINABLE_CASES, PLURAL_INDECLINABLE_CASES), 115 | } 116 | 117 | 118 | def decline(lastname, gram_form=''): 119 | ''' Склоняет фамилию и возвращает все возможные формы ''' 120 | 121 | # Из фамилии выделяется предполагаемая лемма (Табуретов -> Табуретов, 122 | # Табуретовым -> Табуретов), лемма склоняется по правилам склонения фамилий 123 | 124 | def guess_lemma(name): 125 | ''' 126 | Попытаться угадать сложносклоняемую фамилию (Цапок, Бегунец, Берия) 127 | 128 | Возвращает пару (name=lemma+suffix, lemma) либо (None, None) 129 | ''' 130 | 131 | name_len = len(name) 132 | 133 | # Попытка угадать склонённую фамилию из 13.1.12 ("Берией") 134 | if name_len > 2 and name[-2:] in ('ИИ', 'ИЮ',): 135 | return (lastname[:-2] + 'ИЯ', lastname[:-2]) 136 | elif name_len > 3 and name[-3:] in ('ИЕЙ',): 137 | return (lastname[:-3] + 'ИЯ', lastname[:-3]) 138 | 139 | # Попытка угадать склонённую фамилию, закачивающуюся на -ок ("Цапка") 140 | # Работает, только если буква перед окончанием согласная. 141 | # Проверка согласной делается для исключения склонённых фамилий на -ак 142 | # ("Собчака") 143 | if name_len > 3 and name[-2:] in ('КА', 'КУ', 'КЕ',) and name[-3] in CONSONANTS: 144 | return (lastname[:-2] + 'ОК', lastname[:-2]) 145 | elif name_len > 4 and name[-3:] in ('КОМ',) and name[-4] in CONSONANTS: 146 | return (lastname[:-3] + 'ОК', lastname[:-3]) 147 | 148 | # Попытка угадать склонённую фамилию, закачивающуюся на -ец ("Бегунец") 149 | # FIXME: необходима проверка на коллизии с другими фамилиями (как в 150 | # случае с "Цапок") 151 | if name_len > 3 and name[-2:] in ('ЦА', 'ЦУ', 'ЦЕ',): 152 | return (lastname[:-2] + 'ЕЦ', lastname[:-2]) 153 | 154 | return (None, None) 155 | 156 | 157 | match = LASTNAME_PATTERN.search(lastname) 158 | lemma = name = match.group(1) if match else lastname # name is lemma + suffix 159 | name_len = len(name) 160 | 161 | guessed_name, guessed_lemma = guess_lemma(name) 162 | if guessed_name and guessed_lemma: 163 | name, lemma = guessed_name, guessed_lemma 164 | 165 | cases, plural_cases = {}, () 166 | if name_len > 2: 167 | cases, plural_cases = CASEMAP.get(name[-2:], ({}, ())) 168 | if cases: 169 | lemma = name[:-2] 170 | 171 | if not cases and name_len > 3: 172 | cases, plural_cases = CASEMAP.get(name[-3:], ({}, ())) 173 | if cases: 174 | lemma = name[:-3] 175 | 176 | # В случае 13.1.12 лемма состоит из фамилии, за исключением 177 | # двух последних букв 178 | if cases is CASES_IA or cases is CASES_OK: 179 | lemma = name = name[:-2] 180 | 181 | if not cases: 182 | return [] 183 | 184 | expected_form = GramForm(gram_form) 185 | 186 | forms = [] 187 | for i, case in zip(range(6), ('им', 'рд', 'дт', 'вн', 'тв', 'пр',)): 188 | for gender_tag in ('мр', 'жр',): 189 | form = GramForm('%s,%s,фам,ед' % (case, gender_tag,)) 190 | 191 | if gram_form and not form.match(expected_form): 192 | continue 193 | 194 | forms.append({ 195 | 'word': '%s%s' % (name, cases[gender_tag][i]), 196 | 'class': 'С', 197 | 'info': form.get_form_string(), 198 | 'lemma': name, 199 | 'method': 'decline_lastname (%s)' % lastname, 200 | 'norm': '%s%s' % (name, cases[gender_tag][0]), 201 | }) 202 | 203 | plural_form = GramForm('%s,мр-жр,фам,мн' % (case,)) 204 | 205 | if gram_form and not plural_form.match(expected_form): 206 | continue 207 | 208 | forms.append({ 209 | 'word': '%s%s' % (name, plural_cases[i]), 210 | 'class': 'С', 211 | 'info': plural_form.get_form_string(), 212 | 'lemma': name, 213 | 'method': 'decline_lastname (%s)' % lastname, 214 | 'norm': '%s%s' % (name, plural_cases[0]), 215 | }) 216 | 217 | # Просклонять рекурсивно для случая с множественным числом фамилии. 218 | # Козловых -> фам,им; Козловых (мн) -> Козлов -> фам,им 219 | if lemma != name and LASTNAME_PATTERN.match(lemma): 220 | refinement = decline(lemma) 221 | if refinement: 222 | return forms + refinement 223 | 224 | return forms 225 | 226 | 227 | def normalize(morph, lastname, hints=''): 228 | ''' 229 | Возвращает нормальную форму (именительный падеж) фамилии для заданного рода 230 | 231 | Параметры: 232 | 233 | * hints - подсказки об исходной форме фамилии ('мр' или 'жр', 234 | по-умолчанию принимается 'мр') 235 | ''' 236 | 237 | hints_form = GramForm(hints) 238 | gender_tag = (hints_form.match_string('жр') or 'мр') 239 | 240 | # FIXME: эта функция возвращает саму форму, а Morph.normalize возвращает 241 | # множество (set) возможных форм, одно из двух лучше поправить. 242 | return inflect(morph, lastname, 'им,ед,%s' % gender_tag) 243 | 244 | 245 | def inflect(morph, lastname, gram_form): 246 | ''' 247 | Вернуть вариант фамилии который соотвествует данной грамматической 248 | форме 249 | 250 | Параметры: 251 | 252 | * morph - объект Morph 253 | * lastname - фамилия которую хотим склонять 254 | * gram_form - желаемые характеристики грам. формы (если 'жр' отсутствует 255 | в этом параметре, то по-умолчанию принимается 'мр', или 'мр-жр', если 256 | указано 'мн') 257 | ''' 258 | 259 | expected_form = GramForm(gram_form) 260 | 261 | gender_tag = ('мр-жр' if expected_form.match_string('мн') else None) 262 | if not gender_tag: 263 | gender_tag = (expected_form.match_string('жр') or 'мр') 264 | 265 | # За один проход проверяется, что исходное слово может быть склонено как 266 | # фамилия и выбирается форма подходящая под gram_form 267 | 268 | present_in_decline = False 269 | accepted = {} 270 | for item in decline(lastname): 271 | form = GramForm(item.get('info', '')) 272 | 273 | # Если в результате склонения не получилось исходной формы - ложное срабатывание 274 | 275 | # Обязательно проверяется род: при склонении в противоположном роде 276 | # может получиться исходная форма но нас интересует совпадение только в 277 | # заданном роде 278 | 279 | if item.get('word', '') == lastname: 280 | # В случае склонения во множественную форму, род игнорируется. 281 | # Род всех фамилий во множественном числе - мр-жр. 282 | if expected_form.match_string('мн') or form.match_string(gender_tag): 283 | present_in_decline = True 284 | 285 | expected = form.match(expected_form) 286 | 287 | if expected and not accepted: 288 | accepted = item 289 | # Здесь break не нужен т.к. present_in_decline всё ещё может быть 290 | # не установлена в корректное значение 291 | 292 | # Если в результате склонения исходной формы не получилось, 293 | # возвращается результат склонения как для обычного слова 294 | 295 | if present_in_decline and accepted: 296 | return accepted.get('word', '') 297 | else: 298 | return morph.inflect_ru(lastname, gram_form, smart_guess=False) 299 | 300 | 301 | def get_graminfo(lastname): 302 | '''Вернуть грамматическую информацию о фамилии и её нормальную форму''' 303 | 304 | info = [] 305 | for item in decline(lastname): 306 | if item.get('word', '') == lastname: 307 | info.append(item) 308 | 309 | return info 310 | 311 | 312 | def pluralize(morph, lastname, gram_form=''): 313 | ''' 314 | Вернуть фамилию во множественном числе. 315 | 316 | Параметры: 317 | 318 | * morph - объект Morph 319 | * lastname - фамилия которую хотим склонять 320 | * gram_form - желаемые характеристики грам. формы 321 | ''' 322 | 323 | expected_form = GramForm(gram_form) 324 | 325 | # Удалить из желаемой формы признаки рода и числа 326 | refined_form = GramForm(gram_form).clear_gender().clear_number() 327 | 328 | # Если дан gram_form - склонить в указанную форму 329 | if refined_form.get_form_string(): 330 | return inflect( 331 | morph, 332 | lastname, 333 | ','.join((refined_form.get_form_string(), 'мн',))) 334 | 335 | # Иначе - найти форму исходной фамилии и склонить в неё же, но во мн. числе 336 | # Если в желаемой форме был указан род - использовать как подсказку 337 | gender_tag = (expected_form.match_string('жр') or 'мр') 338 | 339 | for item in decline(lastname): 340 | form = GramForm(item.get('info', '')) 341 | 342 | # Проверить наличие исходной формы в заданном роде (аналогично inflect()) 343 | if item.get('word', '') == lastname and form.match_string(gender_tag): 344 | for case in ('им', 'рд', 'дт', 'вн', 'тв', 'пр'): 345 | if form.match_string(case): 346 | return inflect(morph, lastname, 'мн,%s' % case) 347 | 348 | # В случае неудачи - просклонять как обычное слово 349 | return morph.pluralize_ru(lastname, gram_form) 350 | 351 | 352 | def pluralize_inflected(morph, lastname, num, hints=''): 353 | ''' 354 | Вернуть фамилию в форме, которая будет сочетаться с переданным числом. 355 | Например: 1 Попугаев, 2 Попугаевых, 5 Попугаевых. 356 | 357 | Параметры: 358 | 359 | * morph - объект Morph 360 | * lastname - фамилия которую хотим склонять 361 | * num - число 362 | * hints - подсказки об исходной форме фамилии ('мр' или 'жр') 363 | ''' 364 | 365 | if num == 1: 366 | return normalize(morph, lastname, hints) 367 | 368 | hints_form = GramForm(hints) 369 | 370 | gender_tag = (hints_form.match_string('жр') or 'мр') 371 | return pluralize(morph, lastname, 'мн,рд,%s' % gender_tag) 372 | -------------------------------------------------------------------------------- /pymorphy/contrib/scan.py: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | from __future__ import unicode_literals 3 | 4 | def get_graminfo_scan(morph, word, standard=False): 5 | ''' Вернуть грам. информацию, считая, что текст - после сканирования. ''' 6 | 7 | # Сначала пытаемся найти слово по словарю, без предсказателя, 8 | # пробуя различные характерные для отсканированных документов замены. 9 | 10 | gram = [] 11 | word = word.replace('0', 'О') # заменяем всегда 12 | forms = morph.get_graminfo(word, standard, False, predict_hyphenated=False) 13 | if forms: 14 | return forms 15 | 16 | replaces = [('4', 'А'), ('Ф', 'О'), ('J', 'А'), ('Ы', 'А')] 17 | # сначала пробуем найти все в словаре после замен 18 | for bad, good in replaces: 19 | if bad in word: 20 | forms = morph.get_graminfo(word.replace(bad, good), standard, False, predict_hyphenated=False) 21 | gram.extend(forms) 22 | if gram: 23 | return gram 24 | # если найти не удалось, то пробуем уже эвристику 25 | for bad, good in replaces: 26 | if bad in word: 27 | forms = morph.get_graminfo(word.replace(bad, good), standard, False, predict_hyphenated=True) 28 | gram.extend(forms) 29 | if gram: 30 | return gram 31 | # если это не помогло, включаем предсказатель и ищем по старинке 32 | return morph.get_graminfo(word, standard) 33 | 34 | -------------------------------------------------------------------------------- /pymorphy/contrib/tokenizers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import re 3 | 4 | SPACE_REGEX = re.compile('[^\w_-]|[+]', re.U) 5 | GROUPING_SPACE_REGEX = re.compile('([^\w_-]|[+])', re.U) 6 | 7 | def extract_tokens(text): 8 | """ 9 | Разбивает текст на токены - слова, пробелы, знаки препинания и возвращает 10 | полученный массив строк. 11 | """ 12 | return filter(None, GROUPING_SPACE_REGEX.split(text)) 13 | 14 | 15 | def extract_words(text): 16 | """ 17 | Разбивает текст на слова. Пунктуация игнорируется. 18 | Слова, пишущиеся через дефис, считаются 1 словом. 19 | Пример использования:: 20 | 21 | from pymorphy.contrib import tokenizers 22 | 23 | for word in tokenizers.extract_words(text): 24 | print word 25 | 26 | Возвращает генератор, выдающий слова из текста (не list). 27 | 28 | """ 29 | for word in SPACE_REGEX.split(text): 30 | test_word = word.replace('-','') 31 | if not test_word or test_word.isspace() or test_word.isdigit(): 32 | continue 33 | word = word.strip('-') 34 | yield word 35 | -------------------------------------------------------------------------------- /pymorphy/contrib/word_case.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import 3 | 4 | def restore_word_case(word, original_word): 5 | """ 6 | Восстанавливает регистр слова (расположение строчных-заглавных букв). 7 | 8 | * word - слово, у которого требуется восстановить 9 | регистр (в любом регистре); 10 | * original_word - исходное слово (в исходном регистре); 11 | 12 | Пример: 13 | 14 | >>> from pymorphy.contrib.word_case import restore_word_case 15 | >>> print restore_word_case(u'ЛЮДИ', u'Человек') 16 | Люди 17 | 18 | Если сопоставить регистр слов не удается, то результат возвращается 19 | в нижнем регистре. 20 | """ 21 | if '-' in original_word: 22 | parts = zip(word.split('-'), original_word.split('-')) 23 | return '-'.join(restore_word_case(*p) for p in parts) 24 | 25 | if original_word.isupper(): 26 | return word.upper() 27 | elif original_word.islower(): 28 | return word.lower() 29 | elif original_word.istitle(): 30 | return word.title() 31 | else: 32 | return word.lower() 33 | 34 | # TODO: декоратор? -------------------------------------------------------------------------------- /pymorphy/django_conf.py: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | """ 3 | Минимальный пример настроек словарей в settings.py:: 4 | 5 | PYMORPHY_DICTS = { 6 | 'ru': { 'dir': '/usr/share/pymorphy/ru' }, 7 | } 8 | 9 | Более сложный пример:: 10 | 11 | PYMORPHY_DICTS = { 12 | 'ru': { 13 | 'dir': '/usr/share/pymorphy/ru', 14 | 'backend': 'tcb', 15 | 'use_cache': True, 16 | }, 17 | 18 | 'en': { 19 | 'dir': '/usr/share/pymorphy/en', 20 | 'backend': 'shelve', 21 | 'use_cache': True, 22 | 'default': True 23 | }, 24 | } 25 | 26 | """ 27 | 28 | from django.conf import settings 29 | 30 | from django.core.exceptions import ImproperlyConfigured 31 | 32 | from pymorphy.morph import get_morph 33 | 34 | 35 | default_morph = None 36 | try: 37 | PYMORPHY_DICTS = settings.PYMORPHY_DICTS 38 | morphs = {} 39 | for dict_name in PYMORPHY_DICTS: 40 | options = {'backend': 'sqlite', 'use_cache': True, 'default': False} 41 | options.update(PYMORPHY_DICTS[dict_name]) 42 | morphs[dict_name] = get_morph(options['dir'], options['backend'], options['use_cache']) 43 | if default_morph is None or options['default']: 44 | default_morph = morphs[dict_name] 45 | 46 | except AttributeError: 47 | raise ImproperlyConfigured('correct settings.PYMORPHY_DICTS is required for pymorphy template tags.') 48 | 49 | MARKER_OPEN = getattr(settings, 'PYMORPHY_MARKER_OPEN', '\[\[') 50 | MARKER_CLOSE = getattr(settings, 'PYMORPHY_MARKER_CLOSE', '\]\]') 51 | -------------------------------------------------------------------------------- /pymorphy/models.py: -------------------------------------------------------------------------------- 1 | # without this file django won't be able to find templatetags -------------------------------------------------------------------------------- /pymorphy/morph.py: -------------------------------------------------------------------------------- 1 | try: 2 | from pymorphy_speedups._morph import * 3 | 4 | #noinspection PyUnresolvedReferences 5 | from pymorphy.version import speedups_version_is_correct 6 | if not speedups_version_is_correct(): 7 | from pymorphy._morph import * 8 | 9 | except ImportError: 10 | from pymorphy._morph import * 11 | -------------------------------------------------------------------------------- /pymorphy/morph_tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmike/pymorphy/9a6614449447c6ceee35e6d0dfe36903ebc80c5f/pymorphy/morph_tests/__init__.py -------------------------------------------------------------------------------- /pymorphy/morph_tests/base.py: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | from __future__ import unicode_literals, absolute_import 3 | try: 4 | from django.utils import unittest as unittest2 5 | except ImportError: 6 | try: 7 | import unittest2 8 | except ImportError: 9 | import unittest as unittest2 10 | assert hasattr(unittest2, 'expectedFailure') 11 | 12 | from pymorphy.py3k import PY3 13 | 14 | from .dicts import morph_en, morph_ru 15 | from pymorphy.morph import GramForm 16 | from pymorphy.contrib.scan import get_graminfo_scan 17 | 18 | class MorphTestCase(unittest2.TestCase): 19 | 20 | def _msg(self, fmt, w1, w2): 21 | if PY3: 22 | return None 23 | 24 | # console fix for python 2 25 | return fmt.encode('utf8') % (w1.encode('utf8'), w2.encode('utf8')) 26 | 27 | def assertEqualRu(self, word1, word2): 28 | self.assertEqual(word1, word2, self._msg('%s != %s', word1, word2)) 29 | 30 | def assertNotEqualRu(self, word1, word2): 31 | self.assertNotEqual(word1, word2, self._msg('%s == %s', word1, word2)) 32 | 33 | def assertNormal(self, input, output): 34 | norm_forms = morph_ru.normalize(input) 35 | correct_norm_forms = set(output) 36 | 37 | msg = "[%s] != [%s]" % (", ".join(norm_forms), ", ".join(correct_norm_forms)) 38 | self.assertEqual(norm_forms, correct_norm_forms, msg.encode('utf8')) 39 | 40 | def assertNormalEn(self, input, output): 41 | self.assertEqual(morph_en.normalize(input), set(output)) 42 | 43 | def assertPlural(self, word, plural, *args, **kwargs): 44 | morphed_word = morph_ru.pluralize_ru(word, *args, **kwargs) 45 | self.assertEqualRu(morphed_word, plural) 46 | 47 | def assertInflected(self, word, form, result, *args, **kwargs): 48 | morphed_word = morph_ru.inflect_ru(word, form, *args, **kwargs) 49 | self.assertEqualRu(morphed_word, result) 50 | 51 | def assertHasInfo(self, word, norm=None, cls=None, method=None, scan=False, form=None, standard=False, has_info=True): 52 | 53 | def is_correct(frm): 54 | correct = True 55 | if norm: 56 | correct = frm['norm'] == norm 57 | if method: 58 | correct = correct and (method in frm['method']) 59 | if cls: 60 | correct = correct and (frm['class'] == cls) 61 | if form: 62 | gram_filter = GramForm(form) 63 | gram_form = GramForm(frm['info']) 64 | correct = correct and gram_form.match(gram_filter) 65 | return correct 66 | 67 | if scan: 68 | forms = get_graminfo_scan(morph_ru, word, standard=standard) 69 | else: 70 | forms = morph_ru.get_graminfo(word, standard=standard) 71 | self.assertEqual(any([is_correct(frm) for frm in forms]), has_info) 72 | 73 | def assertStandard(self, word, norm, cls=None, form=None, has_info=True, scan=False): 74 | self.assertHasInfo(word, norm, cls, None, scan, form, True, has_info) 75 | 76 | 77 | class TestMorph(MorphTestCase): 78 | 79 | def test_normalize(self): 80 | self.assertNormal('КОШКА', ['КОШКА']) 81 | self.assertNormal('КОШКЕ', ['КОШКА']) 82 | self.assertNormal('СТАЛИ', ['СТАЛЬ', 'СТАТЬ']) 83 | 84 | self.assertNormalEn('SOLD', ['SELL']) 85 | self.assertNormalEn('COMPUTERS', ['COMPUTER']) 86 | 87 | def test_global_prefix_normalize(self): 88 | self.assertNormal('ПСЕВДОКОШКА', ['ПСЕВДОКОШКА']) 89 | self.assertNormal('ПСЕВДОКОШКОЙ', ['ПСЕВДОКОШКА']) 90 | 91 | def test_rule_prefix_normalize(self): 92 | self.assertNormal('НАИСТАРЕЙШИЙ', ['СТАРЫЙ']) 93 | self.assertNormal('СВЕРХНАИСТАРЕЙШИЙ', ['СВЕРХСТАРЫЙ']) 94 | self.assertNormal('СВЕРХНАИСТАРЕЙШИЙ', ['СВЕРХСТАРЫЙ']) 95 | self.assertNormal('КВАЗИПСЕВДОНАИСТАРЕЙШЕГО', ['КВАЗИПСЕВДОСТАРЫЙ']) 96 | self.assertNormal('НЕБЕСКОНЕЧЕН', ['НЕБЕСКОНЕЧНЫЙ']) 97 | 98 | def test_prefix_predict(self): 99 | self.assertNormal('МЕГАКОТУ', ['МЕГАКОТ']) 100 | self.assertNormal('МЕГАСВЕРХНАИСТАРЕЙШЕМУ', ['МЕГАСВЕРХСТАРЫЙ']) 101 | 102 | def test_EE_bug(self): 103 | self.assertNormal('КОТЕНОК', ['КОТЕНОК']) 104 | self.assertNormal('ТЯЖЕЛЫЙ', ['ТЯЖЕЛЫЙ']) 105 | self.assertNormal('ЛЕГОК', ['ЛЕГКИЙ']) 106 | # fix dict for this? done. 107 | # should fail if dictionaries are converted using strip_EE=False option 108 | 109 | def test_pronouns(self): 110 | 111 | self.assertNormalEn('SHE', ['SHE']) 112 | self.assertNormalEn('I', ['I']) 113 | self.assertNormalEn('ME', ['I']) 114 | 115 | self.assertNormal('ОНА', ['ОНА']) 116 | self.assertNormal('ЕЙ', ['ОНА']) 117 | self.assertNormal('Я', ['Я']) 118 | self.assertNormal('МНЕ', ['Я']) 119 | # self.assertNormal('ЕГО', ['ОН', 'ОНО']) 120 | # self.assertNormal('ЕМУ', ['ОН', 'ОНО']) 121 | 122 | def test_no_base(self): 123 | self.assertNormal('НАИНЕВЕРОЯТНЕЙШИЙ', ['ВЕРОЯТНЫЙ']) 124 | self.assertNormal('ЛУЧШИЙ', ['ХОРОШИЙ']) 125 | self.assertNormal('НАИЛУЧШИЙ', ['ХОРОШИЙ']) 126 | self.assertNormal('ЧЕЛОВЕК', ['ЧЕЛОВЕК']) 127 | self.assertNormal('ЛЮДИ', ['ЧЕЛОВЕК']) 128 | 129 | def test_predict(self): 130 | self.assertNormal('ТРИЖДЫЧЕРЕЗПИЛЮЛЮОКНАМИ', ['ТРИЖДЫЧЕРЕЗПИЛЮЛЮОКНА']) 131 | self.assertNormal('РАЗКВАКАЛИСЬ',['РАЗКВАКАТЬСЯ']) 132 | self.assertNormal('КАШИВАРНЕЕ', ['КАШИВАРНЫЙ']) 133 | self.assertNormal('ДЕПЫРТАМЕНТОВ',['ДЕПЫРТАМЕНТ']) 134 | self.assertNormal('ИЗМОХРАТИЛСЯ',['ИЗМОХРАТИТЬСЯ']) 135 | 136 | def test_no_prod_classes_in_prediction(self): 137 | self.assertNormal('БУТЯВКОЙ',['БУТЯВКА']) # и никаких местоимений! 138 | self.assertNormal('САПАЮТ',['САПАТЬ']) # и никаких местоимений! 139 | 140 | def test_female(self): 141 | self.assertNormal('КЛЮЕВУ', ['КЛЮЕВ']) 142 | self.assertNormal('КЛЮЕВА', ['КЛЮЕВ']) 143 | 144 | def test_verbs(self): 145 | self.assertNormal('ГУЛЯЛ', ['ГУЛЯТЬ']) 146 | self.assertNormal('ГУЛЯЛА', ['ГУЛЯТЬ']) 147 | self.assertNormal('ГУЛЯЕТ', ['ГУЛЯТЬ']) 148 | self.assertNormal('ГУЛЯЮТ', ['ГУЛЯТЬ']) 149 | self.assertNormal('ГУЛЯЛИ', ['ГУЛЯТЬ']) 150 | self.assertNormal('ГУЛЯТЬ', ['ГУЛЯТЬ']) 151 | 152 | def test_verb_products(self): 153 | self.assertNormal('ГУЛЯЮЩИЙ', ['ГУЛЯТЬ']) 154 | self.assertNormal('ГУЛЯВШИ', ['ГУЛЯТЬ']) 155 | self.assertNormal('ГУЛЯЯ', ['ГУЛЯТЬ']) 156 | self.assertNormal('ГУЛЯЮЩАЯ', ['ГУЛЯТЬ']) 157 | self.assertNormal('ЗАГУЛЯВШИЙ', ['ЗАГУЛЯТЬ']) 158 | 159 | def test_drop_gender(self): 160 | self.assertNormal('КРАСИВЫЙ', ['КРАСИВЫЙ']) 161 | self.assertNormal('КРАСИВАЯ', ['КРАСИВЫЙ']) 162 | self.assertNormal('КРАСИВОМУ', ['КРАСИВЫЙ']) 163 | self.assertNormal('КРАСИВЫЕ', ['КРАСИВЫЙ']) 164 | 165 | def test_encoding_bugs(self): 166 | self.assertNormal('ДЕЙСТВИЕ', ['ДЕЙСТВИЕ']) 167 | 168 | 169 | class TestGramInfo(unittest2.TestCase): 170 | 171 | def test_lemma_graminfo(self): 172 | info = morph_ru.get_graminfo('СУСЛИКАМИ') 173 | self.assertEqual(len(info), 1) 174 | info = info[0] 175 | gram_form = GramForm(info['info']) 176 | self.assertEqual(gram_form.form, set(['мр', 'тв', 'мн'])) 177 | self.assertEqual(info['norm'], 'СУСЛИК') 178 | -------------------------------------------------------------------------------- /pymorphy/morph_tests/data/__init__.py: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | 3 | from basic import * -------------------------------------------------------------------------------- /pymorphy/morph_tests/data_parsing.py: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | from __future__ import unicode_literals, print_function 3 | 4 | from dicts import morph_ru 5 | from data.basic import BASIC_TESTS 6 | 7 | def parse_test_data(test): 8 | lines = test.splitlines() 9 | data = {} 10 | word = None 11 | for line in lines: 12 | parts = line.split() 13 | if len(parts) == 1: 14 | word = parts[0].upper().replace('Ё', 'Е') 15 | data[word] = [] 16 | elif len(parts) == 2: 17 | data[word].append({'norm': parts[0], 'class': parts[1], 'info': ''}) 18 | elif len(parts) == 3: 19 | data[word].append({'norm': parts[0], 'class': parts[1], 'info': parts[2]}) 20 | else: 21 | print ("!!====", line) 22 | for word in data: 23 | for form in data[word]: 24 | form['norm'] = form['norm'].upper().replace('Ё', 'Е') 25 | return data 26 | 27 | def _normal_form_match(expected_norm, actual_norm): 28 | variants = [form.strip("()") for form in expected_norm.split('|')] 29 | return any([norm == actual_norm for norm in variants]) 30 | 31 | def _gram_info_match(expected_info, actual_info): 32 | actual_grammems = set([gr for gr in actual_info.split(',') if gr]) 33 | 34 | if expected_info.startswith('[') and expected_info.endswith(']'): 35 | # вся информация опциональна 36 | expected = expected_info.strip('[]').replace('[,', ',[').split(',') 37 | must_grammems = set([]) 38 | allowed_grammems = set([gr.strip('[]') for gr in expected]) 39 | else: 40 | expected = expected_info.replace('[,', ',[').split(',') 41 | must_grammems = set([gr for gr in expected if not gr.startswith('[')]) 42 | allowed_grammems = set([gr.strip('[]') for gr in expected]) 43 | 44 | # print actual_grammems, ' || ', must_grammems, ' | ' , allowed_grammems 45 | 46 | return must_grammems.issubset(actual_grammems) and \ 47 | allowed_grammems.issuperset(actual_grammems) 48 | 49 | def forms_match(expected, actual): 50 | """ Совпадает ли ожидаемый результат разбора с тем, что получился """ 51 | return (expected['class'] == actual['class']) and \ 52 | _normal_form_match(expected['norm'], actual['norm']) and \ 53 | _gram_info_match(expected['info'], actual['info']) 54 | 55 | def _format_tag(tag): 56 | return ",".join([tag['class']]+(tag['info'].split(',') or [])) 57 | 58 | 59 | def _format_row(word, expected, actual): 60 | return "['%s', '%s', '%s']," % (word, _format_tag(expected), _format_tag(actual)) 61 | 62 | def main(): 63 | # print _gram_info_match('[imper,1p,pl]', '1p,pl,imper') 64 | # exit() 65 | 66 | all = 0 67 | failed1 = 0 68 | failed2 = 0 69 | 70 | failed_words = {} 71 | 72 | for test_data in BASIC_TESTS: 73 | collection = parse_test_data(test_data) 74 | for word in collection: 75 | expected_results = collection[word] 76 | actual_results = morph_ru.get_graminfo(word, standard=True) 77 | actual_results_aot = morph_ru.get_graminfo(word) 78 | 79 | # проверяем, что все стандартные варианты разбора учтены 80 | for expected_form in expected_results: 81 | match = any([forms_match(expected_form, actual_form) for actual_form in actual_results]) 82 | if not match: 83 | failed1 += 1 84 | print (' ----', word, expected_form['class'], expected_form['norm'], expected_form['info']) 85 | for actual_form in actual_results: 86 | print (" \____ %s %s %s %s" % (word, actual_form['class'], actual_form['norm'], actual_form['info'])) 87 | all += 1 88 | 89 | # проверяем, нет ли у нас лишних (неправильных) вариантов разбора 90 | for actual_form, actual_form_aot in zip(actual_results, actual_results_aot): 91 | # для каждого варианта разбора смотрим, соответствует ли ему 92 | # хотя бы 1 разбор в стандарте 93 | match = any([forms_match(expected_form, actual_form) for expected_form in expected_results]) 94 | if not match: 95 | print (" ++++ %s %s %s %s" % (word, actual_form['class'], actual_form['norm'], actual_form['info'])) 96 | failed2 += 1 97 | # else: 98 | # for expected_form in expected_results: 99 | # if forms_match(expected_form, actual_form): 100 | # print(_format_row(word, expected_form, actual_form_aot)) 101 | 102 | print (' total: %d, failed: %d, extra: %d' % (all, failed1, failed2)) 103 | 104 | if __name__ == '__main__': 105 | main() 106 | -------------------------------------------------------------------------------- /pymorphy/morph_tests/dicts.py: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | import os 3 | from pymorphy.morph import get_morph 4 | 5 | _path = os.path.join(os.path.dirname(__file__), '..', '..', 'dicts', 'converted') 6 | DICT_PATH = os.path.abspath(_path) 7 | RU_DICT = os.path.join(DICT_PATH, 'ru') 8 | EN_DICT = os.path.join(DICT_PATH, 'en') 9 | 10 | morph_ru = get_morph(RU_DICT) 11 | morph_en = get_morph(EN_DICT) 12 | -------------------------------------------------------------------------------- /pymorphy/morph_tests/dirty.py: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | from __future__ import absolute_import, unicode_literals 3 | from .base import MorphTestCase, unittest2 4 | 5 | class TestScans(MorphTestCase): 6 | 7 | def test_basic(self): 8 | self.assertHasInfo('РАСШИФР0ВКИ', 'РАСШИФРОВКА', 'С', scan=True) 9 | 10 | def test_junky_variants_bug(self): 11 | self.assertStandard('КАБИНЕТЫ', 'КАБИНЕТ', 'S', form='pl') 12 | self.assertStandard('КАБИНЕТЫ', 'КАБИНЕТ', 'S', form='pl', scan=True) 13 | 14 | self.assertStandard('КАБИНЕТЫ', 'КАБИНЕТ', 'S', form='sg', has_info=False) 15 | self.assertStandard('КАБИНЕТЫ', 'КАБИНЕТ', 'S', form='sg', has_info=False, scan=True) 16 | 17 | 18 | class HyphenScanTest(MorphTestCase): 19 | 20 | def test_scan(self): 21 | self.assertHasInfo('ИНТЕРНЕТ-МАГJЗИНА', 'ИНТЕРНЕТ-МАГАЗИН', 'С', 'hyphen-prefix', True) 22 | self.assertHasInfo('PDF-ДФКУМЕНТ0В', 'PDF-ДОКУМЕНТ', 'С', 'hyphen-prefix', True) 23 | self.assertHasInfo('АММИАЧНФ-СЕЛИТР0ВФГО', 'АММИАЧНО-СЕЛИТРОВЫЙ', 'П', 'hyphen-prefix', True) 24 | self.assertHasInfo('БЫСТРО-БЫСТРФ', 'БЫСТРО-БЫСТРО', 'Н', 'word-formation', True) 25 | 26 | def test_scan_dict(self): 27 | self.assertHasInfo('САНКТ-ПЕТЕРБУРГ4', 'САНКТ-ПЕТЕРБУРГ', 'С', 'lemma(С).suffix(АНКТ-ПЕТЕРБУРГА)', True) 28 | self.assertHasInfo('КJКИХ-ТО', 'КАКОЙ-ТО', 'МС-П', 'suffix(ИХ-ТО)', True) 29 | 30 | def test_scan_error_in_left(self): 31 | self.assertHasInfo('КОМJНД-УЧАСТНИЦ', 'КОМАНДА-УЧАСТНИЦА', 'С', 'word-formation', True) 32 | 33 | @unittest2.expectedFailure 34 | def test_scan_no_error(self): 35 | self.assertHasInfo('ФЕСТИВАЛЬ-КОНКУРС', 'ФЕСТИВАЛЬ-КОНКУРС', 'С', 'word-formation', True) 36 | self.assertNormal('ФИЗИКО-ХИМИЯ', 'ФИЗИКО-ХИМИЯ', scan=True) 37 | 38 | @unittest2.expectedFailure 39 | def test_scan_needs_left_prediction(self): 40 | self.assertHasInfo('КОВАБJНД-УЧАСТНИЦ', 'КОВАБАНДА-УЧАСТНИЦА', 'С', 'word-formation', True) 41 | 42 | 43 | 44 | if __name__ == '__main__': 45 | unittest2.main() 46 | -------------------------------------------------------------------------------- /pymorphy/morph_tests/graminfo.py: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | from __future__ import unicode_literals 3 | from pymorphy.morph_tests.base import unittest2 4 | from pymorphy.morph import GramForm 5 | 6 | class GramFormTest(unittest2.TestCase): 7 | 8 | def test_from_str(self): 9 | form = GramForm('мн,рд') 10 | self.assertTrue('рд' in form.form) 11 | self.assertTrue('мн' in form.form) 12 | 13 | def test_form_change(self): 14 | form = GramForm('мн,рд,мр') 15 | form.update('дт') 16 | self.assertTrue('дт' in form.form) 17 | self.assertTrue('мн' in form.form) 18 | self.assertFalse('рд' in form.form) 19 | 20 | def test_multi_form_change(self): 21 | form = GramForm('мн,рд,мр') 22 | form.update('дт,ед') 23 | self.assertTrue('дт' in form.form) 24 | self.assertTrue('ед' in form.form) 25 | self.assertFalse('рд' in form.form) 26 | self.assertFalse('мн' in form.form) 27 | 28 | def test_form_str(self): 29 | form = GramForm('мр,мн,рд') 30 | self.assertTrue(form.get_form_string().count('мр') == 1) 31 | self.assertTrue(form.get_form_string().count('мн') == 1) 32 | self.assertTrue(form.get_form_string().count('рд') == 1) 33 | self.assertTrue(len(form.get_form_string()) == (2*3)+2) 34 | form.update('дт') 35 | self.assertTrue(form.get_form_string().count('мр') == 1) 36 | self.assertTrue(form.get_form_string().count('мн') == 1) 37 | self.assertTrue(form.get_form_string().count('дт') == 1) 38 | self.assertTrue(len(form.get_form_string()) == (2*3)+2) 39 | 40 | def test_match(self): 41 | form = GramForm("мр,ед,имя") 42 | self.assertTrue(form.match(GramForm("мр"))) 43 | self.assertTrue(form.match(GramForm("ед,мр"))) 44 | 45 | def test_match_inverted(self): 46 | form = GramForm("мр,ед,имя") 47 | self.assertFalse(form.match(GramForm("мр,!имя"))) 48 | self.assertTrue(form.match(GramForm("ед,!тв"))) 49 | 50 | def test_match_string(self): 51 | form = GramForm('мр,ед,им') 52 | self.assertEqual(form.match_string('мр'), 'мр') 53 | self.assertEqual(form.match_string('ед'), 'ед') 54 | self.assertEqual(form.match_string('им'), 'им') 55 | self.assertEqual(form.match_string('!имя'), '!имя') 56 | self.assertFalse(form.match_string('жр')) 57 | 58 | -------------------------------------------------------------------------------- /pymorphy/morph_tests/hyphen.py: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | from __future__ import unicode_literals, absolute_import 3 | from .base import MorphTestCase, unittest2 4 | 5 | class TestHyphen(MorphTestCase): 6 | 7 | def test_dict(self): 8 | self.assertHasInfo('САНКТ-ПЕТЕРБУРГА', 'САНКТ-ПЕТЕРБУРГ', 'С', 'lemma(С).suffix(АНКТ-ПЕТЕРБУРГА)') 9 | 10 | def test_immutable_left(self): 11 | self.assertHasInfo('ИНТЕРНЕТ-МАГАЗИНА', 'ИНТЕРНЕТ-МАГАЗИН', 'С', 'hyphen-prefix') 12 | self.assertHasInfo('PDF-ДОКУМЕНТОВ', 'PDF-ДОКУМЕНТ', 'С', 'hyphen-prefix') 13 | self.assertHasInfo('АММИАЧНО-СЕЛИТРОВОГО', 'АММИАЧНО-СЕЛИТРОВЫЙ', 'П', 'hyphen-prefix') 14 | 15 | def test_mutable_left(self): 16 | self.assertHasInfo('БЫСТРО-БЫСТРО', 'БЫСТРО-БЫСТРО', 'Н', 'word-formation') 17 | self.assertHasInfo('КОМАНД-УЧАСТНИЦ', 'КОМАНДА-УЧАСТНИЦА', 'С', 'word-formation') 18 | self.assertHasInfo('БЕГАЕТ-ПРЫГАЕТ', 'БЕГАТЬ-ПРЫГАТЬ', 'Г', 'word-formation') 19 | self.assertHasInfo('ДУЛ-НАДУВАЛСЯ', 'ДУТЬ-НАДУВАТЬСЯ', 'Г', 'word-formation') 20 | 21 | def test_extra_prefix(self): 22 | self.assertHasInfo('ПОЧТОВО-БАНКОВСКИЙ', 'ПОЧТОВО-БАНКОВСКИЙ', 'П', 'hyphen-prefix(ПОЧТОВО)') 23 | self.assertHasInfo('ПО-ПРЕЖНЕМУ', 'ПО-ПРЕЖНЕМУ', 'Н', 'lemma(ПО-ПРЕЖНЕМУ)') 24 | self.assertHasInfo('ПО-ПРЕЖНЕМУ', 'ПРЕЖНИЙ', 'П', 'hyphen-prefix()') 25 | 26 | @unittest2.expectedFailure 27 | def test_train_bug(self): 28 | self.assertHasInfo('ПОЕЗДОВ-ЭКСПРЕССОВ', 'ПОЕЗД-ЭКСПРЕСС', 'С', 'word-formation') 29 | self.assertHasInfo('ПОДРОСТКАМИ-ПРАКТИКАНТАМИ', 'ПОДРОСТОК-ПРАКТИКАНТ') 30 | self.assertHasInfo('ПОДВОДНИКОВ-СЕВЕРОМОРЦЕВ', 'ПОДВОДНИК-СЕВЕРОМОРЕЦ', 'С', 'word-formation') 31 | 32 | def test_strip_hyphens(self): 33 | self.assertStandard('ПО-ПРЕЖНЕМУ', 'ПРЕЖНИЙ') 34 | self.assertStandard('ПО-ПРЕЖНЕМУ', 'ПРЕЖНИЙ', scan=True) 35 | 36 | def test_rostov(self): 37 | self.assertStandard('РОСТОВЕ-НА-ДОНУ', 'РОСТОВ-НА-ДОНУ') 38 | 39 | class InflectHyphenTest(MorphTestCase): 40 | 41 | def test_inflect_prefix(self): 42 | self.assertInflected('ИНТЕРНЕТ-МАГАЗИН', 'дт,мн', 'ИНТЕРНЕТ-МАГАЗИНАМ') 43 | 44 | @unittest2.expectedFailure 45 | def test_inflect_word_formation(self): 46 | self.assertInflected('ЧЕЛОВЕК-ГОРА', 'дт,ед', 'ЧЕЛОВЕКУ-ГОРЕ') 47 | 48 | def test_inflect_rostov(self): 49 | self.assertInflected('РОСТОВ-НА-ДОНУ', 'пр', 'РОСТОВЕ-НА-ДОНУ') 50 | 51 | 52 | class PluralHyphenTest(MorphTestCase): 53 | 54 | def test_plural(self): 55 | self.assertPlural('ИНТЕРНЕТ-МАГАЗИН', 'ИНТЕРНЕТ-МАГАЗИНЫ') 56 | 57 | @unittest2.expectedFailure 58 | def test_plural_word_formation(self): 59 | self.assertPlural('ЧЕЛОВЕК-ГОРА', 'ЛЮДИ-ГОРЫ') 60 | 61 | if __name__ == '__main__': 62 | unittest2.main() 63 | -------------------------------------------------------------------------------- /pymorphy/morph_tests/thread_bugs.py: -------------------------------------------------------------------------------- 1 | # coding: utf8 2 | from __future__ import unicode_literals, absolute_import 3 | import random 4 | from .base import MorphTestCase 5 | from .dicts import morph_ru 6 | 7 | # http://www.caktusgroup.com/blog/2009/05/26/testing-django-views-for-concurrency-issues/ 8 | def test_concurrently(times): 9 | """ 10 | Add this decorator to small pieces of code that you want to test 11 | concurrently to make sure they don't raise exceptions when run at the 12 | same time. E.g., some Django views that do a SELECT and then a subsequent 13 | INSERT might fail when the INSERT assumes that the data has not changed 14 | since the SELECT. 15 | """ 16 | def test_concurrently_decorator(test_func): 17 | def wrapper(*args, **kwargs): 18 | exceptions = [] 19 | import threading 20 | def call_test_func(): 21 | try: 22 | test_func(*args, **kwargs) 23 | except Exception as e: 24 | exceptions.append(e) 25 | raise 26 | threads = [] 27 | for i in range(times): 28 | threads.append(threading.Thread(target=call_test_func)) 29 | for t in threads: 30 | t.start() 31 | for t in threads: 32 | t.join() 33 | if exceptions: 34 | raise Exception('test_concurrently intercepted %s exceptions: %s' % (len(exceptions), exceptions)) 35 | return wrapper 36 | return test_concurrently_decorator 37 | 38 | 39 | class SqliteThreadingTest(MorphTestCase): 40 | 41 | @test_concurrently(100) 42 | def test_sqlite(self): 43 | words = {1: 'КОММЕНТАРИЙ', 2: 'КОММЕНТАРИЯ'} 44 | num = random.choice([1,2]) 45 | inflected = morph_ru.pluralize_inflected_ru('КОММЕНТАРИЙ', num) 46 | self.assertEqualRu(inflected, words[num]) 47 | 48 | -------------------------------------------------------------------------------- /pymorphy/morph_tests/tokenizers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import, unicode_literals 3 | from pymorphy.morph_tests.base import unittest2 4 | from pymorphy.contrib.tokenizers import extract_tokens, extract_words 5 | 6 | class SplitTest(unittest2.TestCase): 7 | 8 | def assertSplitted(self, text, words): 9 | self.assertEqual(list(extract_tokens(text)), words) 10 | 11 | def test_split_simple(self): 12 | self.assertSplitted('Мама мыла раму', ['Мама', ' ', 'мыла', ' ', 'раму']) 13 | self.assertSplitted('Постой, паровоз!', ['Постой', ',', ' ', 'паровоз', '!']) 14 | 15 | def test_split_hyphen(self): 16 | self.assertSplitted('Ростов-на-Дону', ['Ростов-на-Дону']) 17 | self.assertSplitted('Ура - победа', ['Ура', ' ', '-', ' ', 'победа']) 18 | 19 | def test_split_signs(self): 20 | self.assertSplitted('a+b=c_1', ['a','+','b','=','c_1']) 21 | 22 | 23 | class ExtractWordsTest(unittest2.TestCase): 24 | 25 | def test_exctract_words(self): 26 | txt = '''Это отразилось: на количественном,и на качествен_ном 27 | - росте карельско-финляндского сотрудничества - офигеть! кони+лошади=масло. 28 | -сказал кто-то --нет--''' 29 | words = list(extract_words(txt)) 30 | self.assertListEqual(words, [ 31 | 'Это', 'отразилось', 'на', 'количественном', 'и', 'на', 32 | 'качествен_ном', 'росте', 'карельско-финляндского', 33 | 'сотрудничества', 'офигеть', 'кони', 'лошади', 'масло', 34 | 'сказал', 'кто-то', 'нет', 35 | ]) 36 | 37 | if __name__ == '__main__': 38 | unittest2.main() 39 | -------------------------------------------------------------------------------- /pymorphy/morph_tests/utilities.py: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | from __future__ import absolute_import, unicode_literals 3 | from .dicts import morph_ru 4 | from .base import MorphTestCase, unittest2 5 | 6 | class TestPluraliseRu(MorphTestCase): 7 | 8 | def test_nouns(self): 9 | self.assertPlural('ГОРОД', 'ГОРОДА') 10 | self.assertPlural('СТАЛЬ', 'СТАЛИ') 11 | self.assertPlural('СТАЛЕВАРОМ', 'СТАЛЕВАРАМИ') 12 | 13 | def test_predictor_nouns(self): 14 | self.assertPlural('БУТЯВКОЙ', 'БУТЯВКАМИ') 15 | 16 | def test_verbs(self): 17 | self.assertPlural('ГУЛЯЛ', 'ГУЛЯЛИ') 18 | self.assertPlural('ГУЛЯЛА', 'ГУЛЯЛИ') 19 | self.assertPlural('РАСПРЫГИВАЕТСЯ', 'РАСПРЫГИВАЮТСЯ') 20 | 21 | def test_prefix(self): 22 | self.assertPlural('СУПЕРКОТ', 'СУПЕРКОТЫ') 23 | 24 | def test_predict_by_suffix(self): 25 | self.assertPlural('ДЕПЫРТАМЕНТ', 'ДЕПЫРТАМЕНТЫ') 26 | self.assertPlural('ХАБР', 'ХАБРЫ') 27 | 28 | def test_invalid_word(self): 29 | self.assertPlural('123', '123') 30 | 31 | def test_invalid_graminfo(self): 32 | self.assertPlural('НАЧАЛО', 'НАЧАЛА', gram_class='С') 33 | 34 | 35 | class TestInflectRu(MorphTestCase): 36 | 37 | def test_inflect(self): 38 | self.assertInflected("СУСЛИК", "дт", "СУСЛИКУ") 39 | self.assertInflected("СУСЛИКИ", "дт", "СУСЛИКАМ") 40 | self.assertInflected("СУСЛИКОВ", "дт", "СУСЛИКАМ") 41 | self.assertInflected("СУСЛИКА", "дт", "СУСЛИКУ") 42 | self.assertInflected("СУСЛИК", "мн,дт", "СУСЛИКАМ") 43 | 44 | def test_verbs(self): 45 | self.assertInflected("ГУЛЯЮ", "прш", "ГУЛЯЛ") 46 | self.assertInflected("ГУЛЯЛ", "нст", "ГУЛЯЮ") 47 | 48 | def test_loc2(self): 49 | self.assertInflected('ЛЕС', 'пр', 'ЛЕСЕ') # о лесе 50 | self.assertInflected('ЛЕС', 'пр,2', 'ЛЕСУ') # в лесу 51 | 52 | # о велосипеде 53 | self.assertInflected('ВЕЛОСИПЕД', 'пр', 'ВЕЛОСИПЕДЕ') 54 | 55 | # а тут второго предложного нет, в велосипеде 56 | self.assertInflected('ВЕЛОСИПЕД', 'пр,2', 'ВЕЛОСИПЕДЕ') 57 | 58 | def test_decline_bug(self): 59 | self.assertInflected('ОРЕЛ', 'рд', 'ОРЛА') 60 | 61 | def test_improper_guess(self): 62 | self.assertInflected('ОСТРОВА', 'дт', 'ОСТРОВАМ') 63 | 64 | def test_improper_guess2(self): 65 | self.assertInflected('КИЕВ', 'пр', 'КИЕВЕ') 66 | 67 | def test_animacy(self): 68 | self.assertInflected('СЛАБЫЙ', 'мр,вн,но', 'СЛАБЫЙ') 69 | self.assertInflected('СЛАБЫЙ', 'мр,вн,од', 'СЛАБОГО') 70 | self.assertInflected('СЛАБЫЙ', 'мр,ед,вн,но', 'СЛАБЫЙ') 71 | self.assertInflected('СЛАБЫЙ', 'мр,ед,вн,од', 'СЛАБОГО') 72 | 73 | def test_comparative(self): 74 | self.assertInflected('БЫСТРЫЙ', 'сравн', 'БЫСТРЕЕ') 75 | self.assertInflected('ХОРОШАЯ', 'сравн', 'ЛУЧШЕ') 76 | 77 | class TestPluralizeInflected(MorphTestCase): 78 | 79 | def assertInflectedPlural(self, word, count, result, *args, **kwargs): 80 | morphed_word = morph_ru.pluralize_inflected_ru(word, count, *args, **kwargs) 81 | self.assertEqualRu(morphed_word, result) 82 | 83 | def test_parrots(self): 84 | self.assertInflectedPlural("ПОПУГАЙ", 1, "ПОПУГАЙ") 85 | self.assertInflectedPlural("ПОПУГАЙ", 2, "ПОПУГАЯ") 86 | self.assertInflectedPlural("ПОПУГАЙ", 3, "ПОПУГАЯ") 87 | self.assertInflectedPlural("ПОПУГАЙ", 4, "ПОПУГАЯ") 88 | self.assertInflectedPlural("ПОПУГАЙ", 5, "ПОПУГАЕВ") 89 | self.assertInflectedPlural("ПОПУГАЙ", 7, "ПОПУГАЕВ") 90 | self.assertInflectedPlural("ПОПУГАЙ", 9, "ПОПУГАЕВ") 91 | self.assertInflectedPlural("ПОПУГАЙ", 0, "ПОПУГАЕВ") 92 | self.assertInflectedPlural("ПОПУГАЙ", 10, "ПОПУГАЕВ") 93 | self.assertInflectedPlural("ПОПУГАЙ", 11, "ПОПУГАЕВ") 94 | self.assertInflectedPlural("ПОПУГАЙ", 12, "ПОПУГАЕВ") 95 | self.assertInflectedPlural("ПОПУГАЙ", 15, "ПОПУГАЕВ") 96 | self.assertInflectedPlural("ПОПУГАЙ", 19, "ПОПУГАЕВ") 97 | self.assertInflectedPlural("ПОПУГАЙ", 21, "ПОПУГАЙ") 98 | self.assertInflectedPlural("ПОПУГАЙ", 32, "ПОПУГАЯ") 99 | self.assertInflectedPlural("ПОПУГАЙ", 38, "ПОПУГАЕВ") 100 | self.assertInflectedPlural("ПОПУГАЙ", 232, "ПОПУГАЯ") 101 | self.assertInflectedPlural("ПОПУГАЙ", 111, "ПОПУГАЕВ") 102 | self.assertInflectedPlural("ПОПУГАЙ", 101, "ПОПУГАЙ") 103 | 104 | def test_butyavka(self): 105 | self.assertInflectedPlural("БУТЯВКА", 1, "БУТЯВКА") 106 | self.assertInflectedPlural("БУТЯВКА", 2, "БУТЯВКИ") 107 | self.assertInflectedPlural("БУТЯВКА", 5, "БУТЯВОК") 108 | 109 | def test_adjective(self): 110 | self.assertInflectedPlural('АКТИВНЫЙ', 1, 'АКТИВНЫЙ') 111 | self.assertInflectedPlural('АКТИВНЫЙ', 2, 'АКТИВНЫХ') 112 | self.assertInflectedPlural('АКТИВНЫЙ', 5, 'АКТИВНЫХ') 113 | 114 | self.assertInflectedPlural('АКТИВНАЯ', 1, 'АКТИВНАЯ') 115 | self.assertInflectedPlural('АКТИВНАЯ', 2, 'АКТИВНЫХ') 116 | self.assertInflectedPlural('АКТИВНАЯ', 5, 'АКТИВНЫХ') 117 | 118 | def test_gerund(self): 119 | self.assertInflectedPlural('ИДУЩИЙ', 1, 'ИДУЩИЙ') 120 | self.assertInflectedPlural('ИДУЩИЙ', 2, 'ИДУЩИХ') 121 | self.assertInflectedPlural('ИДУЩИЙ', 5, 'ИДУЩИХ') 122 | 123 | 124 | if __name__ == '__main__': 125 | unittest2.main() 126 | -------------------------------------------------------------------------------- /pymorphy/morph_tests/word_case.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import absolute_import, unicode_literals 3 | from pymorphy.morph_tests.base import unittest2, MorphTestCase 4 | from pymorphy.contrib.word_case import restore_word_case 5 | 6 | class RestoreCaseTest(MorphTestCase): 7 | 8 | def assertRestored(self, word, original_word, result): 9 | self.assertEqualRu(restore_word_case(word, original_word), result) 10 | 11 | def test_upper(self): 12 | self.assertRestored('ЛЮДИ', 'ЧЕЛОВЕК', 'ЛЮДИ') 13 | 14 | def test_lower(self): 15 | self.assertRestored('ЛЮДИ', 'человек', 'люди') 16 | 17 | def test_title(self): 18 | self.assertRestored('ЛЮДИ', 'Человек', 'Люди') 19 | 20 | def test_hyphenated(self): 21 | self.assertRestored('ЛЮДИ-ГОРЫ-МАЛЕНЬКИЙ', 'Человек-ГОРА-небольшая', 'Люди-ГОРЫ-маленький') 22 | 23 | # сейчас не поддерживается, т.к. неясен алгоритм 24 | @unittest2.expectedFailure 25 | def test_mixed(self): 26 | self.assertRestored('ЛюДи', 'ЛюДи', 'ЛюДи') 27 | 28 | if __name__ == '__main__': 29 | unittest2.main() 30 | -------------------------------------------------------------------------------- /pymorphy/py3k.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | PY3 = sys.version_info[0] == 3 4 | 5 | if PY3: 6 | string_types, text_type, binary_type = str, str, bytes 7 | else: 8 | string_types, text_type, binary_type = basestring, unicode, str 9 | -------------------------------------------------------------------------------- /pymorphy/templatetags/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kmike/pymorphy/9a6614449447c6ceee35e6d0dfe36903ebc80c5f/pymorphy/templatetags/__init__.py -------------------------------------------------------------------------------- /pymorphy/templatetags/pymorphy_tags.py: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | from __future__ import unicode_literals, absolute_import 3 | import re 4 | from django import template 5 | 6 | from pymorphy.py3k import text_type 7 | from pymorphy.django_conf import default_morph, MARKER_OPEN, MARKER_CLOSE 8 | from pymorphy.contrib import tokenizers 9 | from pymorphy.contrib.word_case import restore_word_case 10 | 11 | register = template.Library() 12 | 13 | markup_re = re.compile('(%s.+?%s)' % (MARKER_OPEN, MARKER_CLOSE), re.U) 14 | 15 | def _process_phrase(phrase, process_func, *args, **kwargs): 16 | """ обработать фразу """ 17 | words = tokenizers.extract_tokens(phrase) 18 | result="" 19 | try: 20 | for word in words: 21 | if tokenizers.GROUPING_SPACE_REGEX.match(word): 22 | result += word 23 | continue 24 | processed = process_func(word.upper(), *args, **kwargs) 25 | processed = restore_word_case(processed, word) if processed else word 26 | result += processed 27 | except Exception: 28 | return phrase 29 | return result 30 | 31 | 32 | def _process_marked_phrase(phrase, process_func, *args, **kwargs): 33 | """ 34 | Обработать фразу. В фразе обрабатываются только куски, заключенные 35 | в двойные квадратные скобки (например, "[[лошадь]] Пржевальского"). 36 | """ 37 | def process(m): 38 | return _process_phrase(m.group(1)[2:-2], 39 | process_func, *args, **kwargs) 40 | return re.sub(markup_re, process, phrase) 41 | 42 | 43 | def _process_unmarked_phrase(phrase, process_func, *args, **kwargs): 44 | """ 45 | Обработать фразу. В фразе не обрабатываются куски, заключенные 46 | в двойные квадратные скобки (например, "лошадь [[Пржевальского]]"). 47 | """ 48 | def process(part): 49 | if not re.match(markup_re, part): 50 | return _process_phrase(part, process_func, *args, **kwargs) 51 | return part[2:-2] 52 | 53 | parts = [process(s) for s in re.split(markup_re, phrase)] 54 | return "".join(parts) 55 | 56 | 57 | @register.filter 58 | def inflect(phrase, form): 59 | if not phrase: 60 | return phrase 61 | return _process_unmarked_phrase(text_type(phrase), default_morph.inflect_ru, text_type(form)) 62 | 63 | @register.filter 64 | def inflect_marked(phrase, form): 65 | if not phrase: 66 | return phrase 67 | return _process_marked_phrase(text_type(phrase), default_morph.inflect_ru, text_type(form)) 68 | 69 | @register.filter 70 | def plural(phrase, amount): 71 | if not phrase: 72 | return phrase 73 | return _process_unmarked_phrase(phrase, default_morph.pluralize_inflected_ru, amount) 74 | -------------------------------------------------------------------------------- /pymorphy/tests.py: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | from __future__ import unicode_literals, absolute_import 3 | from unittest import TestCase 4 | from django import template 5 | from django.utils.translation import ugettext_lazy as _ 6 | 7 | from pymorphy.py3k import PY3 8 | from .templatetags.pymorphy_tags import inflect, plural, inflect_marked 9 | 10 | class PymorphyDjangoTestCase(TestCase): 11 | def _msg(self, fmt, w1, w2): 12 | if PY3: 13 | return None 14 | 15 | # console fix for python 2 16 | return fmt.encode('utf8') % (w1.encode('utf8'), w2.encode('utf8')) 17 | 18 | 19 | class InflectMarkedTagTest(PymorphyDjangoTestCase): 20 | 21 | def assertInflected(self, phrase, form, result): 22 | inflected_word = inflect_marked(phrase, form) 23 | err_msg = self._msg("%s != %s" , inflected_word, result) 24 | self.assertEqual(inflected_word, result, err_msg) 25 | 26 | def test_basic_no_inflect(self): 27 | self.assertInflected('[[лошадь]] Пржевальского', 'дт', 'лошади Пржевальского') 28 | self.assertInflected('Москва', 'пр', 'Москва') 29 | self.assertInflected('[[Москва]]', 'пр', 'Москве') 30 | self.assertInflected('[[Москва]]-сити', 'пр', 'Москве-сити') 31 | 32 | def test_two_words_no_inflect(self): 33 | self.assertInflected('[[лошадь]] Пржевальского и [[красный конь]] Кузьмы Петрова-Водкина', 34 | 'дт', 35 | 'лошади Пржевальского и красному коню Кузьмы Петрова-Водкина') 36 | 37 | 38 | class InflectTagTest(PymorphyDjangoTestCase): 39 | 40 | def assertInflected(self, phrase, form, result): 41 | inflected_word = inflect(phrase, form) 42 | err_msg = self._msg("%s != %s" , inflected_word, result) 43 | self.assertEqual(inflected_word, result, err_msg) 44 | 45 | def test_word_case(self): 46 | self.assertInflected('Котопес', '', 'Котопес') 47 | self.assertInflected('ВАСЯ', '', 'ВАСЯ') 48 | self.assertInflected('котопес', '', 'котопес') 49 | 50 | def test_one_word(self): 51 | self.assertInflected('Москва', 'пр', 'Москве') 52 | self.assertInflected('бутявка', 'мн,тв', 'бутявками') 53 | self.assertInflected('Петрович', 'дт,отч', 'Петровичу') 54 | 55 | def test_susliki(self): 56 | self.assertInflected('сусликов', 'тв', 'сусликами') 57 | 58 | def test_complex_phrase(self): 59 | self.assertInflected('тридцать восемь попугаев и Удав', 'дт', 60 | 'тридцати восьми попугаям и Удаву') 61 | self.assertInflected('Пятьдесят девять сусликов', 'тв', 'Пятьюдесятью девятью сусликами') 62 | 63 | def test_name(self): 64 | self.assertInflected('Геннадий Петрович', 'вн', 'Геннадия Петровича') 65 | self.assertInflected('Геннадий Петрович', 'дт', 'Геннадию Петровичу') 66 | self.assertInflected('Геннадий Петрович', 'тв', 'Геннадием Петровичем') 67 | self.assertInflected('Геннадий Петрович', 'пр', 'Геннадии Петровиче') 68 | 69 | def test_hyphen(self): 70 | self.assertInflected('Ростов-на-Дону', 'пр', 'Ростове-на-Дону') 71 | 72 | 73 | # тесты для несклоняемых кусков 74 | def test_basic_no_inflect(self): 75 | self.assertInflected('лошадь [[Пржевальского]]', 'дт', 'лошади Пржевальского') 76 | self.assertInflected('[[Москва]]', 'пр', 'Москва') 77 | self.assertInflected('Москва', 'пр', 'Москве') 78 | self.assertInflected('Москва[[-сити]]', 'пр', 'Москве-сити') 79 | 80 | def test_two_words_no_inflect(self): 81 | self.assertInflected('лошадь [[Пржевальского]] и красный конь [[Кузьмы Петрова-Водкина]]', 82 | 'дт', 83 | 'лошади Пржевальского и красному коню Кузьмы Петрова-Водкина') 84 | 85 | 86 | 87 | 88 | class PluralTagTest(PymorphyDjangoTestCase): 89 | def assertPlural(self, phrase, amount, result): 90 | morphed = plural(phrase, amount) 91 | err_msg = self._msg("%s != %s" , morphed, result) 92 | self.assertEqual(morphed, result, err_msg) 93 | 94 | def test_pluralize(self): 95 | self.assertPlural('бутявка', 1, 'бутявка') 96 | self.assertPlural('бутявка', 2, 'бутявки') 97 | self.assertPlural('бутявка', 5, 'бутявок') 98 | self.assertPlural('Бутявка', 1, 'Бутявка') 99 | 100 | def test_phrase(self): 101 | self.assertPlural('Геннадий Петрович', 8, 'Геннадиев Петровичей') 102 | 103 | def test_mixed(self): 104 | self.assertPlural('активный пользователь', 1, 'активный пользователь') 105 | self.assertPlural('активный пользователь', 2, 'активных пользователя') 106 | self.assertPlural('активный пользователь', 3, 'активных пользователя') 107 | self.assertPlural('активный пользователь', 4, 'активных пользователя') 108 | self.assertPlural('активный пользователь', 5, 'активных пользователей') 109 | self.assertPlural('активный пользователь', 10, 'активных пользователей') 110 | self.assertPlural('активный пользователь', 21, 'активный пользователь') 111 | 112 | 113 | class LazyStringTest(PymorphyDjangoTestCase): 114 | 115 | def test_safe_string(self): 116 | tpl = template.Template("{% load pymorphy_tags %}{{ 'конь'|inflect:'дт' }}") 117 | rendered, expected = tpl.render(template.Context()), 'коню' 118 | err_msg = self._msg("%s != %s" , rendered, expected) 119 | self.assertEqual(rendered, expected, err_msg) 120 | 121 | def test_i18n_string(self): 122 | horse = _('конь') 123 | tpl = template.Template("{% load pymorphy_tags %}{{ horses|inflect:'дт' }}") 124 | rendered, expected = tpl.render(template.Context({'horses': horse})), 'коню' 125 | err_msg = self._msg("%s != %s" , rendered, expected) 126 | self.assertEqual(rendered, expected, err_msg) 127 | 128 | -------------------------------------------------------------------------------- /pymorphy/utils.py: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | import sys 3 | from pprint import PrettyPrinter 4 | 5 | # http://softwaremaniacs.org/forum/python/25696/ 6 | class MyPrettyPrinter(PrettyPrinter): 7 | def format(self, *args, **kwargs): 8 | repr, readable, recursive = PrettyPrinter.format(self, *args, **kwargs) 9 | if repr: 10 | if repr[0] in ('"', "'"): 11 | repr = repr.decode('string_escape') 12 | elif repr[0:2] in ("u'", 'u"'): 13 | repr = repr.decode('unicode_escape').encode(sys.stdout.encoding) 14 | return repr, readable, recursive 15 | 16 | def pprint(obj, stream=None, indent=1, width=80, depth=None): 17 | printer = MyPrettyPrinter(stream=stream, indent=indent, width=width, depth=depth) 18 | printer.pprint(obj) 19 | 20 | def get_split_variants(word): 21 | """ Вернуть все варианты разбиения слова на 2 части """ 22 | l = len(word) 23 | vars = [(word[:i], word[i:]) for i in range(1,l)] 24 | vars.append((word, '',)) 25 | return vars 26 | -------------------------------------------------------------------------------- /pymorphy/version.py: -------------------------------------------------------------------------------- 1 | # pymorphy version info 2 | 3 | VERSION = (0, 5, 6) 4 | __version__ = '.'.join(map(str, VERSION)) 5 | 6 | def speedups_version_is_correct(warn=True): 7 | # speedups are available, check if version is correct 8 | try: 9 | from pymorphy_speedups.version import __version__ as speedups_version 10 | except ImportError: # 0.5.1 doesn't have __version__ defined 11 | speedups_version = '0.5.1' 12 | 13 | if __version__ != speedups_version: 14 | if warn: 15 | import warnings 16 | 17 | msg = """ 18 | pymorphy-speedups version (%s) is not the same as pymorphy 19 | version (%s). This can lead to incorrect parsing so *speedups are disabled*. 20 | 21 | Please uninstall pymorphy-speedups to remove this warning or install 22 | the correct pymorphy-speedups version (%s) to enable speedups. 23 | """ % (speedups_version, __version__, __version__) 24 | 25 | warnings.warn(msg) 26 | return False 27 | return True 28 | 29 | -------------------------------------------------------------------------------- /runtests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | python _runtests_nodjango.py 3 | python _runtests_django.py 4 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [build_sphinx] 2 | source-dir = docs/ 3 | build-dir = docs/_build 4 | all_files = 1 5 | 6 | [upload_sphinx] 7 | upload-dir = docs/_build/html 8 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from distutils.core import setup 3 | import warnings 4 | import sys 5 | sys.path.insert(0, '..') 6 | 7 | with warnings.catch_warnings(): 8 | warnings.simplefilter("ignore") 9 | 10 | # this will issue a warning in case of 11 | # pymorphy + pymorphy-speedups update 12 | # using single pip requirements file. 13 | from pymorphy.version import __version__ 14 | 15 | for cmd in ('egg_info', 'develop', 'build_sphinx', 'upload_sphinx'): 16 | if cmd in sys.argv: 17 | from setuptools import setup 18 | 19 | setup( 20 | name = 'pymorphy', 21 | version = __version__, 22 | author = 'Mikhail Korobov', 23 | author_email = 'kmike84@gmail.com', 24 | url = 'https://bitbucket.org/kmike/pymorphy/', 25 | download_url = 'https://bitbucket.org/kmike/pymorphy/get/v%s.zip' % __version__, 26 | 27 | description = 'Morphological analyzer (POS tagger + inflection engine) for Russian and English (+perhaps German) languages.', 28 | long_description = open('README').read() + open('docs/CHANGES.rst').read(), 29 | 30 | license = 'MIT license', 31 | packages = ['pymorphy', 32 | 'pymorphy.contrib', 33 | 'pymorphy.backends', 34 | 'pymorphy.backends.shelve_source', 35 | 'pymorphy.templatetags'], 36 | 37 | classifiers=[ 38 | 'Development Status :: 3 - Alpha', 39 | 'Intended Audience :: Developers', 40 | 'Intended Audience :: Science/Research', 41 | 'License :: OSI Approved :: MIT License', 42 | 'Natural Language :: Russian', 43 | 'Natural Language :: English', 44 | 'Natural Language :: German', 45 | 'Programming Language :: Python', 46 | 'Programming Language :: Python :: 2', 47 | 'Programming Language :: Python :: 2.6', 48 | 'Programming Language :: Python :: 2.7', 49 | 'Programming Language :: Python :: 3', 50 | 'Programming Language :: Python :: 3.2', 51 | 'Framework :: Django', 52 | 'Topic :: Software Development :: Libraries :: Python Modules', 53 | 'Topic :: Scientific/Engineering :: Information Analysis', 54 | 'Topic :: Text Processing :: Linguistic', 55 | ], 56 | ) 57 | -------------------------------------------------------------------------------- /speedups/INSTALL: -------------------------------------------------------------------------------- 1 | 1. pip install pymorphy 2 | 3 | 2. Download and extract dictionary pack 4 | from http://bitbucket.org/kmike/pymorphy/downloads/ 5 | 6 | 3. Use it 7 | 8 | from pymorphy import get_morph 9 | morph = get_morph('path/to/extracted/files') -------------------------------------------------------------------------------- /speedups/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) Mikhail Korobov 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 14 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR 15 | A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 16 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 17 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 18 | OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /speedups/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include INSTALL 2 | include LICENSE 3 | include README.rst 4 | 5 | -------------------------------------------------------------------------------- /speedups/README.rst: -------------------------------------------------------------------------------- 1 | pymorphy-speedups 2 | ================= 3 | 4 | Модуль для ускорения pymorphy. 5 | Не включен в основной пакет, чтобы не усложнять установку. 6 | 7 | -------------------------------------------------------------------------------- /speedups/pymorphy_speedups/__init__.py: -------------------------------------------------------------------------------- 1 | #coding: utf-8 2 | from pymorphy_speedups.version import * 3 | 4 | def setup_psyco(): 5 | ''' Попытаться оптимизировать узкие места с помощью psyco ''' 6 | try: 7 | import psyco 8 | from pymorphy.backends.shelve_source.shelf_with_hooks import ShelfWithHooks 9 | from pymorphy.morph import Morph, GramForm, _get_split_variants 10 | 11 | psyco.bind(Morph._get_graminfo) 12 | psyco.bind(Morph._get_lemma_graminfo) 13 | psyco.bind(GramForm.__init__) 14 | psyco.bind(ShelfWithHooks._getitem__cached) 15 | psyco.bind(ShelfWithHooks._contains__cached) 16 | psyco.bind(_get_split_variants) 17 | except ImportError: 18 | pass 19 | -------------------------------------------------------------------------------- /speedups/pymorphy_speedups/version.py: -------------------------------------------------------------------------------- 1 | # pymorphy-speedups version info 2 | 3 | VERSION = (0, 5, 6) 4 | __version__ = '.'.join(map(str, VERSION)) 5 | -------------------------------------------------------------------------------- /speedups/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | sys.path.insert(0, '..') 4 | 5 | from distutils.core import setup 6 | from distutils.extension import Extension 7 | 8 | from pymorphy_speedups.version import __version__ 9 | 10 | 11 | setup( 12 | name = 'pymorphy-speedups', 13 | version = __version__, 14 | author = 'Mikhail Korobov', 15 | author_email = 'kmike84@gmail.com', 16 | 17 | description = 'Speedups for pymorphy', 18 | long_description = open('README.rst').read(), 19 | 20 | ext_modules = [ 21 | Extension("pymorphy_speedups._morph", ["_morph.c"]), 22 | ], 23 | 24 | license = 'MIT license', 25 | packages = ['pymorphy_speedups'], 26 | requires = ['pymorphy (==%s)' % __version__], 27 | 28 | classifiers=[ 29 | 'Development Status :: 3 - Alpha', 30 | 'Intended Audience :: Developers', 31 | 'Intended Audience :: Science/Research', 32 | 'License :: OSI Approved :: MIT License', 33 | 'Natural Language :: Russian', 34 | 'Natural Language :: English', 35 | 'Natural Language :: German', 36 | 'Programming Language :: C', 37 | 'Programming Language :: Python', 38 | 'Programming Language :: Python :: 2', 39 | 'Programming Language :: Python :: 2.5', 40 | 'Programming Language :: Python :: 2.6', 41 | 'Programming Language :: Python :: 2.7', 42 | 'Topic :: Software Development :: Libraries :: Python Modules', 43 | 'Topic :: Scientific/Engineering :: Information Analysis', 44 | 'Topic :: Text Processing :: Linguistic', 45 | ], 46 | ) 47 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py26,py27,py32,pypy 3 | 4 | [testenv] 5 | deps= 6 | simplejson 7 | unittest2 8 | python-cdb 9 | django==1.3.1 10 | 11 | commands= 12 | python _runtests_nodjango.py 13 | python _runtests_django.py 14 | 15 | [testenv:py32] 16 | deps= 17 | https://bitbucket.org/vinay.sajip/django/get/tip.zip#egg=django 18 | -------------------------------------------------------------------------------- /update_speedups.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cython -X boundscheck=True pymorphy/_morph.py -o speedups/_morph.c 3 | --------------------------------------------------------------------------------