Наследование и создание экземпляра подтипа typing.NamedTuple в HyLang

Я пытаюсь использовать Hy, это диалект Lisp, построенный поверх Python.

Я попытался запустить следующее, но, как и ожидалось, я получаю AttributeError: Cannot overwrite NamedTuple attribute __init__,

(defclass Key [NamedTuple]
  ;; Simple container for holding keywords
  (defn --init-- [self KEY IDX END]
    (setv self.KEY KEY)
    (setv self.IDX IDX)
    (setv self.END END)))

С другой стороны, я не знаю, какой синтаксис использовать для определения переменных поля в классе. Я пробовал следующее, но это поднимает NameError: name 'KEY' is not defined,

(defclass Key [NamedTuple]
  (setv self.KEY KEY)
  (setv self.IDX IDX)
  (setv self.END END))

Итак, как именно я могу установить переменные поля в классе Lispy/Python?

1 ответ

Именованные кортежи из стандартной библиотеки Python не нуждаются в объявлении класса. Вы создаете класс с помощью вызова функции.

=> (import [collections [namedtuple]])
=> (namedtuple 'Point3D '[x y z])
<class '__console__.Point3D'>
=> (setv Point3D _)  ; Hy's repl sets _ to the previous result.
=> (Point3D 1 2 3)
Point3D(x=1, y=2, z=3)

Именованные экземпляры кортежей неизменны, как и обычные кортежи Python. Вы не можете назначить им.

Python позволяет вам устанавливать произвольные атрибуты для большинства объектов. Вот пример этого в Hy.

=> (setv spam (fn[]))
=> (setv spam.x 7)
=> spam.x
7

В приведенном выше примере я установил атрибут x на пустой объект функции. Некоторые объекты не имеют атрибута dict. Вы можете создавать такие объекты самостоятельно, используя __slots__ синтаксис. (См. Документацию Python о том, как это работает.)

В Python (и Hy) self не существует в объявлении класса, только в методах, потому что это первый аргумент. Вот почему вы не можете назначить это. Вы можете просто setv имя напрямую, но это вводит в класс dict, а не в каком-то конкретном случае.

=> (defclass Foo []
... (setv class-foo 7)  ; lives in the class dict
... (defn __init__ [self foo]
...   (setv self.foo foo)))  ; lives in the instance dict
=> Foo.class-foo
7
=> (. (Foo 12) foo)  ; the (.) form accesses attributes.
12
=> (. (Foo 12) class-foo)  ; not in the instance, so look in its class
7

Hy еще не имеет синтаксиса для аннотаций типа Python. NamedTuple метакласс использует их. В некоторых случаях вы можете обойти это, создав __annotations__ диктуй себя.

(import [collections [OrderedDict]]
        [typing [NamedTuple]])

(defclass Key [NamedTuple]
  (setv (get (vars) '__annotations__)
        (doto (OrderedDict)
              (assoc 'KEY KEY
                     'IDX IDX
                     'END END))))

Это должно работать так же, как Python

class Key(NamedTuple):
    KEY: KEY
    IDX: IDX
    END: END

Хотя это на самом деле компилируется в нечто более похожее

class Key(NamedTuple):
    :G_1235 = OrderedDict()
    :G_1235[HySymbol('KEY')] = KEY
    :G_1235[HySymbol('IDX')] = IDX
    :G_1235[HySymbol('END')] = END
    vars()[HySymbol('__annotations__')] = :G_1235

Есть и другие способы создания OrderedDict в Hy, но это один из самых простых. Нам нужно, чтобы аннотации были заказаны, потому что NamedTupleс заказаны.

HySymbol is-строка Python (подкласс), и работает одинаково в большинстве контекстов. :G_1235 это имя Hy Gensym. Это не допустимые идентификаторы Python, но Hy компилируется в деревья абстрактного синтаксиса Python, и AST принимает такие имена. Вы можете сами увидеть, как Hy компилирует вещи в репл с --spy вариант или (disassemble ...) функция либо для самого AST, либо для его приблизительного эквивалента в Python.

Вы также можете указать значение по умолчанию для NamedTuple присваивая имя, которое вы аннотировали в теле класса с setv,


Если вы используете Python 3.6+ (и вам NamedTuple метакласс), то вы можете использовать kwargs, чтобы сделать OrderedDictиз-за PEP 468. Не делай OrderedDictТак в предыдущих версиях, где порядок kwargs не гарантирован.

В Hy,

(defclass Foo [NamedTuple]
  (setv (get (vars) '__annotations__)
        (OrderedDict :name str
                     :ID int)
        name "foo"
        ID 42))

Обратите внимание, что один setv Можно назначить несколько пар. Эти последние две пары используются метаклассом как NamedTuple по умолчанию.

В ответ

=> (Foo)
Foo(name='foo', ID=42)
=> (Foo "bar")
Foo(name='bar', ID=42)
=> _.ID
42
Другие вопросы по тегам