Natasha

Natasha solves basic NLP tasks for Russian language: tokenization, sentence segmentatoin, word embedding, morphology tagging, lemmatization, phrase normalization, syntax parsing, NER tagging, fact extraction.

Library is just a wrapper for lower level tools from Natasha project:

  • Razdel — token, sentence segmentation for Russian
  • Navec — compact Russian embeddings
  • Slovnet — modern deep-learning techniques for Russian NLP, compact models for Russian morphology, syntax, NER.
  • Yargy — rule-based fact extraction similar to Tomita parser.
  • Ipymarkup — NLP visualizations for NER and syntax markups.

Consider using these lower level tools for realword tasks. Natasha models are optimized for news articles, on other domains quality may be worse.

In [1]:
from natasha import (
    Segmenter,
    MorphVocab,
    
    NewsEmbedding,
    NewsMorphTagger,
    NewsSyntaxParser,
    NewsNERTagger,
    
    PER,
    NamesExtractor,
    DatesExtractor,
    MoneyExtractor,
    AddrExtractor,

    Doc
)

segmenter = Segmenter()
morph_vocab = MorphVocab()

emb = NewsEmbedding()
morph_tagger = NewsMorphTagger(emb)
syntax_parser = NewsSyntaxParser(emb)
ner_tagger = NewsNERTagger(emb)

names_extractor = NamesExtractor(morph_vocab)
dates_extractor = DatesExtractor(morph_vocab)
money_extractor = MoneyExtractor(morph_vocab)
addr_extractor = AddrExtractor(morph_vocab)

Getting started

Doc

Doc aggregates annotators, initially it has just text field defined:

In [2]:
text = 'Посол Израиля на Украине Йоэль Лион признался, что пришел в шок, узнав о решении властей Львовской области объявить 2019 год годом лидера запрещенной в России Организации украинских националистов (ОУН) Степана Бандеры. Свое заявление он разместил в Twitter. «Я не могу понять, как прославление тех, кто непосредственно принимал участие в ужасных антисемитских преступлениях, помогает бороться с антисемитизмом и ксенофобией. Украина не должна забывать о преступлениях, совершенных против украинских евреев, и никоим образом не отмечать их через почитание их исполнителей», — написал дипломат. 11 декабря Львовский областной совет принял решение провозгласить 2019 год в регионе годом Степана Бандеры в связи с празднованием 110-летия со дня рождения лидера ОУН (Бандера родился 1 января 1909 года). В июле аналогичное решение принял Житомирский областной совет. В начале месяца с предложением к президенту страны Петру Порошенко вернуть Бандере звание Героя Украины обратились депутаты Верховной Рады. Парламентарии уверены, что признание Бандеры национальным героем поможет в борьбе с подрывной деятельностью против Украины в информационном поле, а также остановит «распространение мифов, созданных российской пропагандой». Степан Бандера (1909-1959) был одним из лидеров Организации украинских националистов, выступающей за создание независимого государства на территориях с украиноязычным населением. В 2010 году в период президентства Виктора Ющенко Бандера был посмертно признан Героем Украины, однако впоследствии это решение было отменено судом. '
doc = Doc(text)
doc
Out[2]:
Doc(text='Посол Израиля на Украине Йоэль Лион признался, чт...)

After applying segmenter two new fields appear sents and tokens:

In [3]:
doc.segment(segmenter)
display(doc)
display(doc.sents[:2])
display(doc.tokens[:5])
Doc(text='Посол Израиля на Украине Йоэль Лион признался, чт..., tokens=[...], sents=[...])
[DocSent(stop=218, text='Посол Израиля на Украине Йоэль Лион признался, чт..., tokens=[...]),
 DocSent(start=219, stop=257, text='Свое заявление он разместил в Twitter.', tokens=[...])]
[DocToken(stop=5, text='Посол'),
 DocToken(start=6, stop=13, text='Израиля'),
 DocToken(start=14, stop=16, text='на'),
 DocToken(start=17, stop=24, text='Украине'),
 DocToken(start=25, stop=30, text='Йоэль')]

After applying morph_tagger and syntax_parser, tokens get 5 new fields id, pos, feats, head_id, rel — annotation in Universal Dependencies format:

In [4]:
doc.tag_morph(morph_tagger)
doc.parse_syntax(syntax_parser)
display(doc.tokens[:5])
[DocToken(stop=5, text='Посол', id='1_1', head_id='1_7', rel='nsubj', pos='NOUN', feats=<Anim,Nom,Masc,Sing>),
 DocToken(start=6, stop=13, text='Израиля', id='1_2', head_id='1_1', rel='nmod', pos='PROPN', feats=<Inan,Gen,Masc,Sing>),
 DocToken(start=14, stop=16, text='на', id='1_3', head_id='1_4', rel='case', pos='ADP'),
 DocToken(start=17, stop=24, text='Украине', id='1_4', head_id='1_1', rel='nmod', pos='PROPN', feats=<Inan,Loc,Fem,Sing>),
 DocToken(start=25, stop=30, text='Йоэль', id='1_5', head_id='1_1', rel='appos', pos='PROPN', feats=<Anim,Nom,Masc,Sing>)]

After applying ner_tagger doc gets spans field with PER, LOC, ORG annotation:

In [5]:
doc.tag_ner(ner_tagger)
display(doc.spans[:5])
[DocSpan(start=6, stop=13, type='LOC', text='Израиля', tokens=[...]),
 DocSpan(start=17, stop=24, type='LOC', text='Украине', tokens=[...]),
 DocSpan(start=25, stop=35, type='PER', text='Йоэль Лион', tokens=[...]),
 DocSpan(start=89, stop=106, type='LOC', text='Львовской области', tokens=[...]),
 DocSpan(start=152, stop=158, type='LOC', text='России', tokens=[...])]

Visualizations

Natasha wraps Ipymarkup to provide ASCII visualizations for morphology, syntax and NER annotations. doc and sents have 3 methods: morph.print(), syntax.print() and ner.print():

In [6]:
doc.ner.print()
Посол Израиля на Украине Йоэль Лион признался, что пришел в шок, узнав
      LOC────    LOC──── PER───────                                   
 о решении властей Львовской области объявить 2019 год годом лидера 
                   LOC──────────────                                
запрещенной в России Организации украинских националистов (ОУН) 
              LOC─── ORG─────────────────────────────────────── 
Степана Бандеры. Свое заявление он разместил в Twitter. «Я не могу 
PER────────────                                ORG────             
понять, как прославление тех, кто непосредственно принимал участие в 
ужасных антисемитских преступлениях, помогает бороться с 
антисемитизмом и ксенофобией. Украина не должна забывать о 
                              LOC────                      
преступлениях, совершенных против украинских евреев, и никоим образом 
не отмечать их через почитание их исполнителей», — написал дипломат. 
11 декабря Львовский областной совет принял решение провозгласить 2019
           ORG──────────────────────                                  
 год в регионе годом Степана Бандеры в связи с празднованием 110-летия
                     PER────────────                                  
 со дня рождения лидера ОУН (Бандера родился 1 января 1909 года). В 
                        ORG                                         
июле аналогичное решение принял Житомирский областной совет. В начале 
                                ORG────────────────────────           
месяца с предложением к президенту страны Петру Порошенко вернуть 
                                          PER────────────         
Бандере звание Героя Украины обратились депутаты Верховной Рады. 
PER────              LOC────                     ORG───────────  
Парламентарии уверены, что признание Бандеры национальным героем 
                                     PER────                     
поможет в борьбе с подрывной деятельностью против Украины в 
                                                  LOC────   
информационном поле, а также остановит «распространение мифов, 
созданных российской пропагандой». Степан Бандера (1909-1959) был 
                                   PER───────────                 
одним из лидеров Организации украинских националистов, выступающей за 
                 ORG─────────────────────────────────                 
создание независимого государства на территориях с украиноязычным 
населением. В 2010 году в период президентства Виктора Ющенко Бандера 
                                               PER─────────── PER──── 
был посмертно признан Героем Украины, однако впоследствии это решение 
                             LOC────                                  
было отменено судом. 
In [7]:
sent = doc.sents[0]
sent.morph.print()
               Посол NOUN|Animacy=Anim|Case=Nom|Gender=Masc|Number=Sing
             Израиля PROPN|Animacy=Inan|Case=Gen|Gender=Masc|Number=Sing
                  на ADP
             Украине PROPN|Animacy=Inan|Case=Loc|Gender=Fem|Number=Sing
               Йоэль PROPN|Animacy=Anim|Case=Nom|Gender=Masc|Number=Sing
                Лион PROPN|Animacy=Anim|Case=Nom|Gender=Masc|Number=Sing
           признался VERB|Aspect=Perf|Gender=Masc|Mood=Ind|Number=Sing|Tense=Past|VerbForm=Fin|Voice=Mid
                   , PUNCT
                 что SCONJ
              пришел VERB|Aspect=Perf|Gender=Masc|Mood=Ind|Number=Sing|Tense=Past|VerbForm=Fin|Voice=Act
                   в ADP
                 шок NOUN|Animacy=Inan|Case=Acc|Gender=Masc|Number=Sing
                   , PUNCT
               узнав VERB|Aspect=Perf|Tense=Past|VerbForm=Conv|Voice=Act
                   о ADP
             решении NOUN|Animacy=Inan|Case=Loc|Gender=Neut|Number=Sing
             властей NOUN|Animacy=Inan|Case=Gen|Gender=Fem|Number=Plur
           Львовской ADJ|Case=Gen|Degree=Pos|Gender=Fem|Number=Sing
             области NOUN|Animacy=Inan|Case=Gen|Gender=Fem|Number=Sing
            объявить VERB|Aspect=Perf|VerbForm=Inf|Voice=Act
                2019 ADJ
                 год NOUN|Animacy=Inan|Case=Acc|Gender=Masc|Number=Sing
               годом NOUN|Animacy=Inan|Case=Ins|Gender=Masc|Number=Sing
              лидера NOUN|Animacy=Anim|Case=Gen|Gender=Masc|Number=Sing
         запрещенной VERB|Aspect=Perf|Case=Gen|Gender=Fem|Number=Sing|Tense=Past|VerbForm=Part|Voice=Pass
                   в ADP
              России PROPN|Animacy=Inan|Case=Loc|Gender=Fem|Number=Sing
         Организации PROPN|Animacy=Inan|Case=Gen|Gender=Fem|Number=Sing
          украинских ADJ|Case=Gen|Degree=Pos|Number=Plur
       националистов NOUN|Animacy=Anim|Case=Gen|Gender=Masc|Number=Plur
                   ( PUNCT
                 ОУН PROPN|Animacy=Inan|Case=Nom|Gender=Fem|Number=Sing
                   ) PUNCT
             Степана PROPN|Animacy=Anim|Case=Gen|Gender=Masc|Number=Sing
             Бандеры PROPN|Animacy=Anim|Case=Gen|Gender=Masc|Number=Sing
                   . PUNCT
In [8]:
sent.syntax.print()
        ┌──► Посол         nsubj
        │    Израиля       
        │ ┌► на            case
        │ └─ Украине       
        │ ┌─ Йоэль         
        │ └► Лион          flat:name
┌─────┌─└─── признался     
│     │ ┌──► ,             punct
│     │ │ ┌► что           mark
│     └►└─└─ пришел        ccomp
│     │   ┌► в             case
│     └──►└─ шок           obl
│         ┌► ,             punct
│ ┌────►┌─└─ узнав         advcl
│ │     │ ┌► о             case
│ │ ┌───└►└─ решении       obl
│ │ │ ┌─└──► властей       nmod
│ │ │ │   ┌► Львовской     amod
│ │ │ └──►└─ области       nmod
│ └─└►┌─┌─── объявить      nmod
│     │ │ ┌► 2019          amod
│     │ └►└─ год           obj
│     └──►┌─ годом         obl
│   ┌─────└► лидера        nmod
│   │ ┌►┌─── запрещенной   acl
│   │ │ │ ┌► в             case
│   │ │ └►└─ России        obl
│ ┌─└►└─┌─── Организации   nmod
│ │     │ ┌► украинских    amod
│ │   ┌─└►└─ националистов nmod
│ │   │   ┌► (             punct
│ │   └►┌─└─ ОУН           parataxis
│ │     └──► )             punct
│ └──────►┌─ Степана       appos
│         └► Бандеры       flat:name
└──────────► .             punct

Lemmatization

Tokens have lemmatize method, it uses pos and feats assigned by morph_tagger to get word normal form. morph_vocab is just a wrapper for Pymorphy2:

In [9]:
for token in doc.tokens:
    token.lemmatize(morph_vocab)
    
{_.text: _.lemma for _ in doc.tokens[:10]}
Out[9]:
{'Посол': 'посол',
 'Израиля': 'израиль',
 'на': 'на',
 'Украине': 'украина',
 'Йоэль': 'йоэль',
 'Лион': 'лион',
 'признался': 'признаться',
 ',': ',',
 'что': 'что',
 'пришел': 'прийти'}

Phrase normalization

Consider phrase "Организации украинских националистов", one can not just inflect every word independently to get normal form: "Организация украинский националист". Spans have method normalize that uses syntax annotation by syntax_parser to inflect phrases:

In [10]:
for span in doc.spans:
    span.normalize(morph_vocab)
    
{_.text: _.normal for _ in doc.spans}
Out[10]:
{'Израиля': 'Израиль',
 'Украине': 'Украина',
 'Йоэль Лион': 'Йоэль Лион',
 'Львовской области': 'Львовская область',
 'России': 'Россия',
 'Организации украинских националистов (ОУН)': 'Организация украинских националистов (ОУН)',
 'Степана Бандеры': 'Степан Бандера',
 'Twitter': 'Twitter',
 'Украина': 'Украина',
 'Львовский областной совет': 'Львовский областной совет',
 'ОУН': 'ОУН',
 'Житомирский областной совет': 'Житомирский областной совет',
 'Петру Порошенко': 'Петр Порошенко',
 'Бандере': 'Бандера',
 'Украины': 'Украина',
 'Верховной Рады': 'Верховная Рада',
 'Бандеры': 'Бандера',
 'Степан Бандера': 'Степан Бандера',
 'Организации украинских националистов': 'Организация украинских националистов',
 'Виктора Ющенко': 'Виктор Ющенко',
 'Бандера': 'Бандера'}

Fact extraction

To split names like "Виктор Ющенко", "Бандера" and "Йоэль Лион" into parts use names_extractor and spans method extract_fact:

In [11]:
for span in doc.spans:
    if span.type == PER:
        span.extract_fact(names_extractor)
    
{_.normal: _.fact.as_dict for _ in doc.spans if _.fact}
Out[11]:
{'Йоэль Лион': {'first': 'Йоэль', 'last': 'Лион'},
 'Степан Бандера': {'first': 'Степан', 'last': 'Бандера'},
 'Петр Порошенко': {'first': 'Петр', 'last': 'Порошенко'},
 'Бандера': {'last': 'Бандера'},
 'Виктор Ющенко': {'first': 'Виктор', 'last': 'Ющенко'}}

Reference

One may use Natasha components independently. It is not mandatory to construct Doc object.

Segmenter

Segmenter is just a wrapper for Razdel, it has two methods tokenize and sentenize:

In [12]:
tokens = list(segmenter.tokenize('Кружка-термос на 0.5л (50/64 см³, 516;...)'))
for token in tokens[:5]:
    print(token)
Token(start=0, stop=13, text='Кружка-термос')
Token(start=14, stop=16, text='на')
Token(start=17, stop=20, text='0.5')
Token(start=20, stop=21, text='л')
Token(start=22, stop=23, text='(')
In [13]:
text = '''
- "Так в чем же дело?" - "Не ра-ду-ют".
 И т. д. и т. п. В общем, вся газета
'''
sents = list(segmenter.sentenize(text))
for sent in sents:
    print(sent)
Sent(start=1, stop=23, text='- "Так в чем же дело?"')
Sent(start=24, stop=40, text='- "Не ра-ду-ют".')
Sent(start=42, stop=57, text='И т. д. и т. п.')
Sent(start=58, stop=77, text='В общем, вся газета')

MorphVocab

MorphVocab is a wrapper for Pymorphy2. MorphVocab adds cache and adapts grammems to Universal Dependencies format:

In [14]:
forms = morph_vocab('стали')
forms
Out[14]:
[MorphForm(normal='стать', pos='VERB', feats={'VerbForm': 'Fin', 'Aspect': 'Perf', 'Number': 'Plur', 'Tense': 'Past', 'Mood': 'Ind'}),
 MorphForm(normal='сталь', pos='NOUN', feats={'Animacy': 'Inan', 'Gender': 'Fem', 'Number': 'Sing', 'Case': 'Gen'}),
 MorphForm(normal='сталь', pos='NOUN', feats={'Animacy': 'Inan', 'Gender': 'Fem', 'Number': 'Sing', 'Case': 'Dat'}),
 MorphForm(normal='сталь', pos='NOUN', feats={'Animacy': 'Inan', 'Gender': 'Fem', 'Number': 'Sing', 'Case': 'Loc'}),
 MorphForm(normal='сталь', pos='NOUN', feats={'Animacy': 'Inan', 'Gender': 'Fem', 'Number': 'Plur', 'Case': 'Nom'}),
 MorphForm(normal='сталь', pos='NOUN', feats={'Animacy': 'Inan', 'Gender': 'Fem', 'Number': 'Plur', 'Case': 'Acc'})]
In [15]:
morph_vocab.__call__.cache_info()
Out[15]:
CacheInfo(hits=214, misses=634, maxsize=10000, currsize=634)

Also MorphVocab adds method lemmatize. Given pos and feats it selects the most suitable morph form and returns its normal field:

In [16]:
morph_vocab.lemmatize('стали', 'VERB', {})
Out[16]:
'стать'
In [17]:
morph_vocab.lemmatize('стали', 'X', {'Case': 'Gen'})
Out[17]:
'сталь'

Embedding

Embedding is a wrapper for Navec — compact pretrained word embeddings for Russian language:

In [18]:
print('Words in vocab + 2 for pad and unk: %d' % len(emb.vocab.words) )
Words in vocab + 2 for pad and unk: 250002
In [19]:
emb['навек'][:10]
Out[19]:
array([ 0.3309305 ,  0.18249014,  0.23347412,  0.14935994, -0.17402406,
       -0.47864223, -0.24524143,  0.15673256, -0.08669729, -0.11727095],
      dtype=float32)

MorphTagger

MorphTagger wraps Slovnet morphology tagger. Tagger has list of words as input and returns markup object. Markup has print method that outputs morph tags ASCII visualization:

In [20]:
words = ['Европейский', 'союз', 'добавил', 'в', 'санкционный', 'список', 'девять', 'политических', 'деятелей']
markup = morph_tagger(words)
markup.print()
         Европейский ADJ|Case=Nom|Degree=Pos|Gender=Masc|Number=Sing
                союз NOUN|Animacy=Inan|Case=Nom|Gender=Masc|Number=Sing
             добавил VERB|Aspect=Perf|Gender=Masc|Mood=Ind|Number=Sing|Tense=Past|VerbForm=Fin|Voice=Act
                   в ADP
         санкционный ADJ|Animacy=Inan|Case=Acc|Degree=Pos|Gender=Masc|Number=Sing
              список NOUN|Animacy=Inan|Case=Acc|Gender=Masc|Number=Sing
              девять NUM|Case=Nom
        политических ADJ|Case=Gen|Degree=Pos|Number=Plur
            деятелей NOUN|Animacy=Anim|Case=Gen|Gender=Masc|Number=Plur

SyntaxParser

SyntaxParser wraps Slovnet syntax parser. Interface is similar to MorphTagger:

In [21]:
words = ['Европейский', 'союз', 'добавил', 'в', 'санкционный', 'список', 'девять', 'политических', 'деятелей']
markup = syntax_parser(words)
markup.print()
      ┌► Европейский  amod
    ┌►└─ союз         nsubj
  ┌─└─── добавил      
  │ ┌──► в            case
  │ │ ┌► санкционный  amod
┌─└►└─└─ список       obl
│   ┌──► девять       nummod:gov
│   │ ┌► политических amod
└──►└─└─ деятелей     nmod

NERTagger

NERTagger wraps Slovnet NER tagger. Interface is similar to MorphTagger but has untokenized text as input:

In [22]:
text = 'Посол Израиля на Украине Йоэль Лион признался, что пришел в шок, узнав о решении властей Львовской области объявить 2019 год годом лидера запрещенной в России Организации украинских националистов (ОУН) Степана Бандеры. Свое заявление он разместил в Twitter. 11 декабря Львовский областной совет принял решение провозгласить 2019 год в регионе годом Степана Бандеры в связи с празднованием 110-летия со дня рождения лидера ОУН (Бандера родился 1 января 1909 года).'
markup = ner_tagger(text)
markup.print()
Посол Израиля на Украине Йоэль Лион признался, что пришел в шок, узнав
      LOC────    LOC──── PER───────                                   
 о решении властей Львовской области объявить 2019 год годом лидера 
                   LOC──────────────                                
запрещенной в России Организации украинских националистов (ОУН) 
              LOC─── ORG─────────────────────────────────────── 
Степана Бандеры. Свое заявление он разместил в Twitter. 11 декабря 
PER────────────                                ORG────             
Львовский областной совет принял решение провозгласить 2019 год в 
ORG──────────────────────                                         
регионе годом Степана Бандеры в связи с празднованием 110-летия со дня
              PER────────────                                         
 рождения лидера ОУН (Бандера родился 1 января 1909 года).
                 ORG                                      

Extractor

In addition to names_extractor Natasha bundles several other extractors: dates_extractor, money_extractor and addr_extractor. All extractors are based on Yargy-parser, meaning that they work correctly only on small predefined set of texts. For realword tasks consider writing your own grammar, see Yargy docs for more.

DatesExtractor

In [23]:
text = '24.01.2017, 2015 год, 2014 г, 1 апреля, май 2017 г., 9 мая 2017 года'
list(dates_extractor(text))
Out[23]:
[Match(
     start=0,
     stop=10,
     fact=Date(
         year=2017,
         month=1,
         day=24
     )
 ),
 Match(
     start=12,
     stop=20,
     fact=Date(
         year=2015,
         month=None,
         day=None
     )
 ),
 Match(
     start=22,
     stop=28,
     fact=Date(
         year=2014,
         month=None,
         day=None
     )
 ),
 Match(
     start=30,
     stop=38,
     fact=Date(
         year=None,
         month=4,
         day=1
     )
 ),
 Match(
     start=40,
     stop=51,
     fact=Date(
         year=2017,
         month=5,
         day=None
     )
 ),
 Match(
     start=53,
     stop=68,
     fact=Date(
         year=2017,
         month=5,
         day=9
     )
 )]

MoneyExtractor

In [24]:
text = '1 599 059, 38 Евро, 420 долларов, 20 млн руб, 20 т. р., 881 913 (Восемьсот восемьдесят одна тысяча девятьсот тринадцать) руб. 98 коп.'
list(money_extractor(text))
Out[24]:
[Match(
     start=0,
     stop=18,
     fact=Money(
         amount=1599059.38,
         currency='EUR'
     )
 ),
 Match(
     start=20,
     stop=32,
     fact=Money(
         amount=420,
         currency='USD'
     )
 ),
 Match(
     start=34,
     stop=44,
     fact=Money(
         amount=20000000,
         currency='RUB'
     )
 ),
 Match(
     start=46,
     stop=54,
     fact=Money(
         amount=20000,
         currency='RUB'
     )
 ),
 Match(
     start=56,
     stop=133,
     fact=Money(
         amount=881913.98,
         currency='RUB'
     )
 )]

NamesExtractor

names_extractor should be applied only to spans of text. To extract single fact use method find:

In [25]:
lines = [
    'Мустафа Джемилев',
    'О. Дерипаска',
    'Фёдор Иванович Шаляпин',
    'Янукович'
]
for line in lines:
    display(names_extractor.find(line))
Match(
    start=0,
    stop=16,
    fact=Name(
        first='Мустафа',
        last='Джемилев',
        middle=None
    )
)
Match(
    start=0,
    stop=12,
    fact=Name(
        first='О',
        last='Дерипаска',
        middle=None
    )
)
Match(
    start=0,
    stop=22,
    fact=Name(
        first='Фёдор',
        last='Шаляпин',
        middle='Иванович'
    )
)
Match(
    start=0,
    stop=8,
    fact=Name(
        first=None,
        last='Янукович',
        middle=None
    )
)

AddrExtractor

In [26]:
lines = [
    'Россия, Вологодская обл. г. Череповец, пр.Победы 93 б',
    '692909, РФ, Приморский край, г. Находка, ул. Добролюбова, 18',
    'ул. Народного Ополчения д. 9к.3'
]
for line in lines:
    display(addr_extractor.find(line))
Match(
    start=0,
    stop=48,
    fact=Addr(
        parts=[AddrPart(
             value='Россия',
             type='страна'
         ),
         AddrPart(
             value='Вологодская',
             type='область'
         ),
         AddrPart(
             value='Череповец',
             type='город'
         ),
         AddrPart(
             value='Победы',
             type='проспект'
         )]
    )
)
Match(
    start=0,
    stop=56,
    fact=Addr(
        parts=[AddrPart(
             value='692909',
             type='индекс'
         ),
         AddrPart(
             value='РФ',
             type='страна'
         ),
         AddrPart(
             value='Приморский',
             type='край'
         ),
         AddrPart(
             value='Находка',
             type='город'
         ),
         AddrPart(
             value='Добролюбова',
             type='улица'
         )]
    )
)
Match(
    start=0,
    stop=29,
    fact=Addr(
        parts=[AddrPart(
             value='Народного Ополчения',
             type='улица'
         ),
         AddrPart(
             value='9к',
             type='дом'
         )]
    )
)
In [ ]: