Analyzer¶
Analyzer(path=None)
Morphological analyzer: for a word form it finds all analyses in the OpenCorpora dictionary - the initial form, the part of speech and the Universal Dependencies features - and orders them by probability. The dictionary is loaded when the first analyzer is created: analyzers with the same dictionary file in a process share it, so a new analyzer is created instantly. The last loaded dictionary stays in the process even without analyzers, so process pool tasks and an analyzer created for a single call do not load the dictionary again.
Exceptions:
FileNotFoundError- the dictionary file does not exist;OSError- the dictionary file cannot be read: no access, the path points to a directory;ValueError- the file is not a ruMorph dictionary or is damaged;TypeError- a path that is neither a string norPathLike.
from rumorph import Analyzer
morph = Analyzer()
morph.analyze("стали")[0]
Analysis(word='стали', lemma='стать', pos='VERB', feats='Aspect=Perf|Mood=Ind|Number=Plur|Tense=Past|VerbForm=Fin|Voice=Mid', labels='intr', prob=0.9753459013836056, source='dictionary')
Parameters¶
| Parameter | Type | Default | Description |
|---|---|---|---|
path |
str / PathLike |
None |
Path to the dictionary file; by default, the dictionary from the package |
The object keeps the dictionary path in path of type Path, read-only. The dictionary file is mapped into memory: processes with the same file share its memory, so every further process with an analyzer takes about 1.4 MB. While an analyzer exists, the file must not be overwritten in place, but it can be replaced with a new file; on Windows an open file can be neither replaced nor deleted. The last loaded dictionary stays open even after the analyzers are deleted - until a dictionary from another file is loaded or the process exits. A dictionary is trusted data, like code: loading rejects a damaged file but not every deliberately crafted one, so dictionaries from untrusted sources should not be loaded.
Methods¶
Analyses of a word form¶
analyze(word)
Analyses of the word form in descending order of probability; the probabilities sum to 1. Case does not matter, and the letter «е» may stand for «ё»: analyze("ЕЖ") returns the analyses of the word form «ёж». A token that is not in the dictionary - a number, a symbol, a Latin word, an abbreviation - is analyzed by rules, and the analyses of an unknown Cyrillic word are guessed from its ending and parts. A token without spaces and invisible characters gets at least one analysis: if nothing fits, X without features.
| Parameter | Type | Default | Description |
|---|---|---|---|
word |
str |
- | Word form |
Exceptions:
TypeError- a word that is not a string.
[(a.lemma, a.pos, a.feats.get("Case"), round(a.prob, 3)) for a in morph.analyze("стали")]
[(a.lemma, a.source) for a in morph.analyze("чатботу")][:2]
[('стать', 'VERB', None, 0.975),
('сталь', 'NOUN', 'Gen', 0.011),
('сталь', 'NOUN', 'Nom', 0.005),
('сталь', 'NOUN', 'Dat', 0.003),
('сталь', 'NOUN', 'Loc', 0.003),
('сталь', 'NOUN', 'Acc', 0.003)]
[('чатбот', 'guess'), ('чатбота', 'guess')]
Analyses of a list of word forms¶
analyze_many(words)
Analyses of each word form, as with analyze; on a list the method is faster than analyze in a loop.
| Parameter | Type | Default | Description |
|---|---|---|---|
words |
Iterable[str] |
- | Word forms: a list, a tuple, a generator |
Exceptions:
TypeError- a string instead of a collection of words, a word that is not a string.
[len(analyses) for analyses in morph.analyze_many(["мама", "мыла", "раму"])]
[1, 4, 2]
Lemmatization¶
lemmatize(words)
Lemmas of the most probable analyses of the word forms; a token without analyses is returned in lowercase.
| Parameter | Type | Default | Description |
|---|---|---|---|
words |
Iterable[str] |
- | Word forms: a list, a tuple, a generator |
Exceptions:
TypeError- a string instead of a collection of words, a word that is not a string.
morph.lemmatize("Мы стали друзьями в ковиде".split())
['мы', 'стать', 'друг', 'в', 'ковид']
Word form in the dictionary¶
is_known(word)
Whether the word form is in the dictionary; as with analyze, case and «е» instead of «ё» do not matter. Words analyzed by rules or guessed are not in the dictionary.
| Parameter | Type | Default | Description |
|---|---|---|---|
word |
str |
- | Word form |
Exceptions:
TypeError- a word that is not a string.
morph.is_known("Ежи"), morph.is_known("вайб")
(True, False)
Dictionary¶
The package dictionary is built from the OpenCorpora dictionary: about 3.1 million word forms. The probabilities of analyses come from the OpenCorpora corpus: a word written with «е» instead of «ё» gets the frequencies of the spelling with «ё», and word forms missing from the corpus get equally probable analyses. The dictionary is distributed under the CC BY-SA 3.0 license.
Threads and processes¶
One analyzer can be used from several threads: analyze_many and lemmatize release the GIL and process lists in parallel, and on the free-threaded Python 3.14t analyze, is_known, inflect and agree run in parallel too. A shared analyzer is not slower than a separate one per thread, so there is no need for an analyzer per thread. Without the GIL threads are limited by deleting the analysis objects: forms hardly speeds up in threads and analyze_many on long lists speeds up weakly, so such lists are better analyzed in chunks of a thousand or two words. The analyzer and analyses are pickled, so they work with multiprocessing and joblib: processes share the dictionary mapped into memory, however many analyses they receive. Passing analyses between processes costs more than the analysis itself, so in processes it is better to get lemmas and analysis fields, and analyses in threads.
from concurrent.futures import ThreadPoolExecutor
texts = [["мама", "мыла", "раму"], ["кошки", "ловят", "мышей"]]
with ThreadPoolExecutor(2) as pool:
lemmas = list(pool.map(morph.lemmatize, texts))
lemmas
[['мама', 'мыло', 'рама'], ['кошка', 'ловить', 'мышь']]