Наследование и создание экземпляра подтипа 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