natasha / yargy

Rule-based facts extraction for Russian language
MIT License
315 stars 40 forks source link

Class-based fact definition (using Python 3.6 annotations) #55

Closed kc41 closed 6 years ago

kc41 commented 6 years ago

Всем привет.

Реализовал малоинвазивный способ объявлять классы фактов с использованием аннотаций типов из Py3.6. Вот пример, как при помощи этого способа создать класс факта.

class Ork(FactDefinition):
    first_name: str
    last_name: str

    @property
    def full_name(self):
        return f"{self.first_name} {self.last_name}"

Это будет эквивалент следующего:

OrkAutoGen = fact("OrkAutoGen", [
    "first_name",
    "last_name",
])

class Ork(OrkAutoGen):
    @property
    def full_name(self):
        return f"{self.first_name} {self.last_name}"
kc41 commented 6 years ago

Это пока proof-of-concept. В TODO'шках дописаны вещи, над которыми надо еще подумать, а так же доделать обратную совместимость с Python < 3.6.

dveselov commented 6 years ago

Привет, идея хорошая, мне нравится. Если сохранится обратная совместимость, то будет вообще круто.

kc41 commented 6 years ago

Ок, тогда как для старых питонов починю - переоткрою PR.

kuk commented 6 years ago
  1. Надо понять что улучшает этот патч. Уменьшается размер кода и появляется проверка типов? По поводу размера, можно сделать как namedtuple чтобы было покороче

    class Ork(fact('Ork', 'first_name, last_name')):
    ...

    По поводу проверки типов, ок, но на практике, вроде с этим не возникает проблем

  2. Атрибуты бывают repeatable, fact('Fact', [attribute('items').repeatable()]) , как это будет выглядеть? У атрибутов бывает значение по умолчанию fact('Fact', [attribute('day', 1), attribute('month', 1)]). Это видимо будет day: str = 1?

  3. 3 раза перечитал патч, ничё не понял, оч сложно, мне недоступны такие приёмы программирования на Питоне. Зачем _ROOT_FACT_DEFINITION, BASE_FACT_DEFINITION_CLS

kc41 commented 6 years ago

1

По поводу того, что улучшает данный патч:

class Ork(FactDefinition): first_name: Iterable[str]

Если нужны будут совсем кастомные дескрипторы атрибутов:
```python
from yargy.typing import AttributeDescriptor  # Или более подходящий по семантике/методам класс или сделать свой

class Ork(FactDefinition):
    first_name: str = AttributeDescriptor(default_value="default_name", prop_1="val_1", prop_2=True)

3

Ну, метаклассы, они такие =) В стандартной библиотеке с NamedTuple (https://docs.python.org/3/library/typing.html#typing.NamedTuple) примерно так же сделано, как и в этом PR.


На данный момент, это PoC, реализованный на коленке за полчаса и сам я еще не очень хорошо знаком с библиотекой, но мне она очень понравилась) Я как раз хотел узнать, насколько мэйнтейнерам интересны подобные улучшения и услышать ваши комментарии на этот счет. Если интерес есть, то возьмусь более основательно)

kuk commented 6 years ago

Ещё вот что подумал: не все атрибуты класса должны быть полями факта. Например, есть факт Name с полями first, last. У него есть атрибут icon чтобы выводить данные в GUI

class Name(fact('Name', ['first', 'last'])):
  icon = 'per.png'
  ...

icon не извлекается из текста. В подходе с аннотациями поля факта и атрибуты класса перемешиваются, так?

kc41 commented 6 years ago

В приведенном примере icon останется полем класса. В __annotations__ попадают только строки вида some_name: some_expression

kuk commented 6 years ago

А так?

class Name(fact('Name', ['first', 'last'])):
  icon: str = 'per.png'
  ...
kc41 commented 6 years ago

А так - да попадет. В текущей реализации - берутся все имена аннотаций и передаются во второй аргумент функции fact() в виде листа.

kuk commented 6 years ago

Можно подумать про такой вариант:

class Name(Fact):
  __fact_attributes__ = ['first', 'last']  # потому что __attributes__ занято Record
  icon = '...'

Но он не про твои улучшения "статический анализатор", "автокомплит"

kc41 commented 6 years ago

Аннотации там сами по себе ничего не меняют. Это просто метки, которые оказываются в атрибуте __annotations__ у класса. Конструкция:

class A():
    a: str
    b: str = B()

Экливалентна следующему объявлению класса:

class A():
    b = B()
    __annotations__ = {"a": str, "b": str}

Подразумевается, что их будут обрабатывать в метаклассе (если обработку надо делать до создания класса) или декораторе над классом (если можно делать обработку после создания класса).