Мне трудно добавить генератор в класс spaCy Token
.
Во-первых, общий 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
в своем собственном код, и это работает просто отлично. В чем проблема?
По причинам, которые я пока не понимаю, избавление от @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']
В вашем общем примере вы получаете доступ к свойству через атрибут класса, что означает запуск протокола дескриптора.
set_extension
, с другой стороны, просто сохраняет ссылку на объект property
в dict
, что означает, что при доступе к нему срабатывает протокол дескриптора нет, и вы получаете сам property
, а не результат геттера.
В вашем обходном пути вы вообще не используете свойство, поэтому вы получаете функцию генератора напрямую.
Что ж, атрибут 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__
(генератор не нужен, но вы можете его использовать, в зависимости от контекста вашей задачи).