Добавить пользовательский генератор в класс spaCy

Мне трудно добавить генератор в класс 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 в своем собственном код, и это работает просто отлично. В чем проблема?

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
0
108
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

По причинам, которые я пока не понимаю, избавление от @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__ (генератор не нужен, но вы можете его использовать, в зависимости от контекста вашей задачи).

Другие вопросы по теме