Добавить собственный генератор в класс spaCy - PullRequest
0 голосов
/ 09 апреля 2019

Мне трудно добавить генератор в класс Token spaCy.

Во-первых, общий Python-эквивалент того, что я пытаюсь сделать, который работает как положено.

class Foo:
    def __init__(self, n):
        self.n = n

@property
def lower_int_generator(self):
    x = 0
    while x < self.n:
        yield x
        x += 1

Foo.lower_ints = lower_int_generator
a = Foo(5)
print(type(a.lower_ints)) # <class 'generator'>
[x for x in a.lower_ints] # [0, 1, 2, 3, 4]

Теперь в spaCy, который предоставляет метод set_extension (см. документация ).

@property
def letter_generator(self):
    for x in self.text:
        yield x

spacy.tokens.token.Token.set_extension('letters', default=letter_generator, force=True)
doc = nlp('Hello world')
print(type(doc[0]._.letters)) # <class 'property'>
[x for x in doc[0]._.letters] # TypeError: 'property' object is not iterable

Примечательно, что spaCy использует @property в своем собственном коде и работает просто отлично. В чем здесь проблема?

Ответы [ 3 ]

1 голос
/ 09 апреля 2019

Ну, атрибут default - это значение, которое возвращается, когда не установлены ни getter, ни setter, следовательно, это то, что было возвращено (свойство или функция, если вы удалите декоратор property). Таким образом вы можете хранить некоторую статическую информацию.

Вы хотите установить getter, как вы это сделали в своем ответе, так как это операция, которая вызывается, когда вы хотите получить значение атрибута. setter должно быть создано при изменении значения, например:

doc[0]._.letters = "A"

setter было бы хорошо предоставить другое значение, чем default, хотя я до сих пор не использовал этот подход.

Наконец, я нашел чистый способ расширения spacy (и IMO более читабельный, чем представленный), пример расширения lemmatization:

class Lemmatizer:
    def __init__(self):
        self.lemmatizer = spacy.lemmatizer.Lemmatizer(
            spacy.lang.en.LEMMA_INDEX,
            spacy.lang.en.LEMMA_EXC,
            spacy.lang.en.LEMMA_RULES,
        )

    def __call__(self, token):
        corrected = token._.text
        if token.text == corrected:
            return token.lemma_
        return self.lemmatizer(corrected, token.pos_)[0]

spacy.tokens.Token.set_extension("lemma", getter=Lemmatizer(), force=True)

Как видите, единственное, что нужно использовать, - это перегруженный метод __call__ (генератор не нужен, но вы также можете использовать его, в зависимости от контекста вашей задачи).

0 голосов
/ 09 апреля 2019

В вашем общем примере вы получаете доступ к свойству через атрибут класса, что означает, что протокол дескриптора запущен.

set_extension, с другой стороны, просто сохраняет ссылку на объект property в dict, что означает, что при обращении к нему протокол дескриптора не сработал, и вы получите сам property, а не результат получателя.

В вашем обходном пути вы вообще не используете свойство, поэтому вы получаете функцию генератора напрямую.

0 голосов
/ 09 апреля 2019

По причинам, которые я еще не понимаю, избавление от @property и использование ключевого слова getter вместо default работает.

def letter_generator(self):
    for x in self.text:
        yield x

spacy.tokens.token.Token.set_extension('letters', getter=letter_generator, force=True)
doc = nlp('Hello world')
print(type(doc[0]._.letters)) # <class 'generator'>
[x for x in doc[0]._.letters] # ['H', 'e', 'l', 'l', 'o']
...