使用 Hy 宏生成 Python 代码
Generating Python code with Hy macros
我正在尝试从 Hy 生成一些 python 代码。如何做得更好?
我尝试了几种方法。一个是宏:
(defmacro make-vars [data]
(setv res '())
(for [element data]
(setv varname (HySymbol (+ "var" (str element))))
(setv res (cons `(setv ~varname 0) res)))
`(do ~@res))
然后在捕获宏展开后,我打印python反汇编代码。
但是,我似乎无法通过宏传递变量,因此:
(setv vnames [1 2 3])
(make-vars vnames)
定义 varv
、varn
、vara
等,而不是 var1
、var2
、var3
。似乎可以通过以下方式进行正确的调用:
(macroexpand `(make-vars ~vnames))
但这似乎过于复杂了。
我遇到的另一个问题是 HySymbol
的必要性,这让我大吃一惊。但是当我尝试第二种方法时,我真的受到了伤害,在那里我创建了一个 returns 引用形式的函数:
(defn make-faction-detaches [faction metadata unit-types]
(let [meta-base (get metadata "Base")
meta-pattern (get metadata "Sections")
class-cand []
class-def '()
class-grouping (dict)]
(for [(, sec-name sec-flag) (.iteritems meta-pattern)]
;; if section flag is set but no unit types with the section are found, break and return nothing
(print "checking" sec-name)
(if-not (or (not sec-flag) (any (genexpr (in sec-name (. ut roles)) [ut unit-types])))
(break)
;; save unit types for section
(do
(print "match for section" sec-name)
(setv sec-grouping (list-comp ut [ut unit-types]
(in sec-name (. ut roles))))
(print (len sec-grouping) "types found for section" sec-name)
(when sec-grouping
(assoc class-grouping sec-name sec-grouping))))
;; in case we finished the cycle
(else
(do
(def
class-name (.format "{}_{}" (. meta-base __name__) (fix-faction-string faction))
army-id (.format "{}_{}" (. meta-base army_id) (fix-faction-string faction))
army-name (.format "{} ({})" (fix-faction-name faction) (. meta-base army_name)))
(print "Class name is" class-name)
(print "Army id is" army-id)
(print "Army name is" army-name)
(setv class-cand [(HySymbol class-name)])
(setv class-def [`(defclass ~(HySymbol class-name) [~(HySymbol (. meta-base __name__))]
[army_name ~(HyString army-name)
faction ~(HyString faction)
army_id ~(HyString army-id)]
(defn --init-- [self]
(.--init-- (super) ~(HyDict (interleave (genexpr (HyString k) [k class-grouping])
(cycle [(HyInteger 1)]))))
~@(map (fn [key]
`(.add-classes (. self ~(HySymbol key))
~(HyList (genexpr (HySymbol (. ut __name__))
[ut (get class-grouping key)]))))
class-grouping)))]))))
(, class-def class-cand)))
该函数采用 python 中的元数据:
metadata = [
{'Base': DetachPatrol,
'Sections': {'hq': True, 'elite': False,
'troops': True, 'fast': False,
'heavy': False, 'fliers': False,
'transports': False}}]
并获取 类 的列表,其形式为:
class SomeSection(object):
roles = ['hq']
它需要广泛使用 hy 的内部 类,而我未能正确表示 True 和 False,而是求助于 HyInteger(1)
和 HyInteger(0)
。
为了从这个函数中得到 python 代码,我 运行 它的结果通过 disassemble
.
总结一下:
- 从 Hy 生成 python 代码的最佳方法是什么?
- True 和 False 的内部表示是什么?
- 能否从宏调用处理其参数的函数和 returns 引用的 Hy 形式?如何调用?
在 Hy 中,您通常不需要生成 Python 代码,因为 Hy 在生成 Hy 代码方面要好得多,就像 executable 一样。这一直在 Hy 宏中完成。
在不寻常的情况下,您需要生成真实的 Python 而不仅仅是 Hy,最好的方法是使用字符串,就像您在 Python 中那样。 Hy 编译成 Python 的 AST,而不是 Python 本身。反汇编程序实际上只是为了调试目的。它并不总是生成有效的 Python:
=> (setv +!@$ 42)
=> +!@$
42
=> (disassemble '(setv +!@$ 42) True)
'+!@$ = 42'
=> (exec (disassemble '(setv +!@$ 42) True))
Traceback (most recent call last):
File "/home/gilch/repos/hy/hy/importer.py", line 193, in hy_eval
return eval(ast_compile(expr, "<eval>", "eval"), namespace)
File "<eval>", line 1, in <module>
File "<string>", line 1
+!@$ = 42
^
SyntaxError: invalid syntax
=> (exec "spam = 42; print(spam)")
42
变量名 +!@$
与 spam
在 AST 中一样合法,但 Python 的 exec
却因为它不是有效变量而窒息Python 标识符。
如果您理解并接受此限制,则可以使用 disassemble
,但不要使用宏。允许普通 运行time 函数获取和生成(如您演示的那样)Hy 表达式。在编译时,宏实际上只是比 运行 这样的函数。在 Hy 中,宏将其部分工作委托给一个普通函数并不少见,该函数将 Hy 表达式作为其参数之一,returns 一个 Hy 表达式。
创建 Hy 表达式作为数据的最简单方法是用 '
引用它。用于插入值的反引号语法即使在宏的主体之外也是有效的。您 也可以 在正常的 运行 时间函数中使用它。但是要明白,如果你想反汇编它,你必须将 引用的形式 插入到插值中,因为这是宏将作为参数接收的内容 - 代码本身 ,而不是它的评估值。这就是您使用 HySymbol
和朋友的原因。
=> (setv class-name 'Foo) ; N.B. 'Foo is quoted
=> (print (disassemble `(defclass ~class-name) True))
class Foo:
pass
您可以询问 REPL 它对引用形式使用什么类型。
=> (type 1)
<class 'int'>
=> (type '1)
<class 'hy.models.HyInteger'>
=> (type "foo!")
<class 'str'>
=> (type '"foo!")
<class 'hy.models.HyString'>
=> (type True)
<class 'bool'>
=> (type 'True)
<class 'hy.models.HySymbol'>
如您所见,True
在内部只是一个符号。请注意,我能够仅使用 '
生成 HySymbol
,而无需使用 HySymbol
调用。如果您的元数据文件是用 Hy 编写的,并且首先使用引用的 Hy 格式制作,则您不必转换它们。但是没有理由必须在反引号表单中的最后一分钟完成。如果您愿意,可以通过辅助函数提前完成。
跟进
Can one call a function that processes its parameters and returns a quoted Hy form from a macro and how?
我原来的观点是宏不是您要执行的操作的错误工具。但需要澄清的是,您可以使用 macroexpand
在 运行 时间调用宏,正如您已经演示的那样。当然,您可以将 macroexpand
调用放在另一个函数中,但是 macroexpand
必须将引用形式作为其参数。
Also, the same question about dynamically generated dictionaries. Construction I have used looks horrible.
字典部分可以简化为
{~@(interleave (map HyString class-grouping) (repeat '1))}
虽然 Python 的 dict
由散列 table 支持,但 Hy 的 HyDict
模型实际上只是一个列表。这是因为它不代表散列 table 本身,而是代表生成字典的 code。这就是为什么你可以像列表一样拼接它。
However if possible, could you add an example of properly passing dynamically generated strings into the final quoted expression? As far as I understand, it can be done with adding one more assignment (that would add quotation), but is there a more elegant way?
Hy's models 被认为是 public API 的一部分,只是在宏之外很少使用。在需要时使用它们很好。其他 Lisp 并没有在代码模型对象和它们产生的数据之间做出同样的区分。 Hy 这样做是为了更好地 Python 互操作。有人可能会争辩说 ~
语法应该为某些数据类型自动执行此转换,但目前并没有。
[更新:在当前的 master 分支上,Hy 的编译器将在可能的情况下自动包装 Hy 模型中的兼容值,因此您通常不必再自己执行此操作。]
HySymbol
适用于像您尝试做的那样从字符串动态生成符号。这不是唯一的方法,但在这种情况下这是您想要的。另一种方式,gensym
,在宏中使用得更多,但它们不能那么漂亮。您可以使用字符串调用 gensym
以为其提供更有意义的名称以用于调试目的,但它仍然具有数字后缀以使其唯一。当然,您可以分配 HySymbol
一个较短的别名,或者将该部分委托给辅助函数。
也可以提前转换,比如片段
(def class-name (.format "{}_{}" (. meta-base __name__) ...
可以改为
(def class-name (HySymbol (.format "{}_{}" (. meta-base __name__) ...
这样就不用重复了
(setv class-cand [class-name])
(setv class-def [`(defclass ~class-name ...
这可能会使模板更易于阅读。
更新
Hy master 现在在编译时将符号分解为有效的 Python 标识符,因此 hy2py
工具和 astor 反汇编应该更可靠地生成有效的 Python 代码,即使有特殊的符号中的字符。
我正在尝试从 Hy 生成一些 python 代码。如何做得更好?
我尝试了几种方法。一个是宏:
(defmacro make-vars [data]
(setv res '())
(for [element data]
(setv varname (HySymbol (+ "var" (str element))))
(setv res (cons `(setv ~varname 0) res)))
`(do ~@res))
然后在捕获宏展开后,我打印python反汇编代码。
但是,我似乎无法通过宏传递变量,因此:
(setv vnames [1 2 3])
(make-vars vnames)
定义 varv
、varn
、vara
等,而不是 var1
、var2
、var3
。似乎可以通过以下方式进行正确的调用:
(macroexpand `(make-vars ~vnames))
但这似乎过于复杂了。
我遇到的另一个问题是 HySymbol
的必要性,这让我大吃一惊。但是当我尝试第二种方法时,我真的受到了伤害,在那里我创建了一个 returns 引用形式的函数:
(defn make-faction-detaches [faction metadata unit-types]
(let [meta-base (get metadata "Base")
meta-pattern (get metadata "Sections")
class-cand []
class-def '()
class-grouping (dict)]
(for [(, sec-name sec-flag) (.iteritems meta-pattern)]
;; if section flag is set but no unit types with the section are found, break and return nothing
(print "checking" sec-name)
(if-not (or (not sec-flag) (any (genexpr (in sec-name (. ut roles)) [ut unit-types])))
(break)
;; save unit types for section
(do
(print "match for section" sec-name)
(setv sec-grouping (list-comp ut [ut unit-types]
(in sec-name (. ut roles))))
(print (len sec-grouping) "types found for section" sec-name)
(when sec-grouping
(assoc class-grouping sec-name sec-grouping))))
;; in case we finished the cycle
(else
(do
(def
class-name (.format "{}_{}" (. meta-base __name__) (fix-faction-string faction))
army-id (.format "{}_{}" (. meta-base army_id) (fix-faction-string faction))
army-name (.format "{} ({})" (fix-faction-name faction) (. meta-base army_name)))
(print "Class name is" class-name)
(print "Army id is" army-id)
(print "Army name is" army-name)
(setv class-cand [(HySymbol class-name)])
(setv class-def [`(defclass ~(HySymbol class-name) [~(HySymbol (. meta-base __name__))]
[army_name ~(HyString army-name)
faction ~(HyString faction)
army_id ~(HyString army-id)]
(defn --init-- [self]
(.--init-- (super) ~(HyDict (interleave (genexpr (HyString k) [k class-grouping])
(cycle [(HyInteger 1)]))))
~@(map (fn [key]
`(.add-classes (. self ~(HySymbol key))
~(HyList (genexpr (HySymbol (. ut __name__))
[ut (get class-grouping key)]))))
class-grouping)))]))))
(, class-def class-cand)))
该函数采用 python 中的元数据:
metadata = [
{'Base': DetachPatrol,
'Sections': {'hq': True, 'elite': False,
'troops': True, 'fast': False,
'heavy': False, 'fliers': False,
'transports': False}}]
并获取 类 的列表,其形式为:
class SomeSection(object):
roles = ['hq']
它需要广泛使用 hy 的内部 类,而我未能正确表示 True 和 False,而是求助于 HyInteger(1)
和 HyInteger(0)
。
为了从这个函数中得到 python 代码,我 运行 它的结果通过 disassemble
.
总结一下:
- 从 Hy 生成 python 代码的最佳方法是什么?
- True 和 False 的内部表示是什么?
- 能否从宏调用处理其参数的函数和 returns 引用的 Hy 形式?如何调用?
在 Hy 中,您通常不需要生成 Python 代码,因为 Hy 在生成 Hy 代码方面要好得多,就像 executable 一样。这一直在 Hy 宏中完成。
在不寻常的情况下,您需要生成真实的 Python 而不仅仅是 Hy,最好的方法是使用字符串,就像您在 Python 中那样。 Hy 编译成 Python 的 AST,而不是 Python 本身。反汇编程序实际上只是为了调试目的。它并不总是生成有效的 Python:
=> (setv +!@$ 42)
=> +!@$
42
=> (disassemble '(setv +!@$ 42) True)
'+!@$ = 42'
=> (exec (disassemble '(setv +!@$ 42) True))
Traceback (most recent call last):
File "/home/gilch/repos/hy/hy/importer.py", line 193, in hy_eval
return eval(ast_compile(expr, "<eval>", "eval"), namespace)
File "<eval>", line 1, in <module>
File "<string>", line 1
+!@$ = 42
^
SyntaxError: invalid syntax
=> (exec "spam = 42; print(spam)")
42
变量名 +!@$
与 spam
在 AST 中一样合法,但 Python 的 exec
却因为它不是有效变量而窒息Python 标识符。
如果您理解并接受此限制,则可以使用 disassemble
,但不要使用宏。允许普通 运行time 函数获取和生成(如您演示的那样)Hy 表达式。在编译时,宏实际上只是比 运行 这样的函数。在 Hy 中,宏将其部分工作委托给一个普通函数并不少见,该函数将 Hy 表达式作为其参数之一,returns 一个 Hy 表达式。
创建 Hy 表达式作为数据的最简单方法是用 '
引用它。用于插入值的反引号语法即使在宏的主体之外也是有效的。您 也可以 在正常的 运行 时间函数中使用它。但是要明白,如果你想反汇编它,你必须将 引用的形式 插入到插值中,因为这是宏将作为参数接收的内容 - 代码本身 ,而不是它的评估值。这就是您使用 HySymbol
和朋友的原因。
=> (setv class-name 'Foo) ; N.B. 'Foo is quoted
=> (print (disassemble `(defclass ~class-name) True))
class Foo:
pass
您可以询问 REPL 它对引用形式使用什么类型。
=> (type 1)
<class 'int'>
=> (type '1)
<class 'hy.models.HyInteger'>
=> (type "foo!")
<class 'str'>
=> (type '"foo!")
<class 'hy.models.HyString'>
=> (type True)
<class 'bool'>
=> (type 'True)
<class 'hy.models.HySymbol'>
如您所见,True
在内部只是一个符号。请注意,我能够仅使用 '
生成 HySymbol
,而无需使用 HySymbol
调用。如果您的元数据文件是用 Hy 编写的,并且首先使用引用的 Hy 格式制作,则您不必转换它们。但是没有理由必须在反引号表单中的最后一分钟完成。如果您愿意,可以通过辅助函数提前完成。
跟进
Can one call a function that processes its parameters and returns a quoted Hy form from a macro and how?
我原来的观点是宏不是您要执行的操作的错误工具。但需要澄清的是,您可以使用 macroexpand
在 运行 时间调用宏,正如您已经演示的那样。当然,您可以将 macroexpand
调用放在另一个函数中,但是 macroexpand
必须将引用形式作为其参数。
Also, the same question about dynamically generated dictionaries. Construction I have used looks horrible.
字典部分可以简化为
{~@(interleave (map HyString class-grouping) (repeat '1))}
虽然 Python 的 dict
由散列 table 支持,但 Hy 的 HyDict
模型实际上只是一个列表。这是因为它不代表散列 table 本身,而是代表生成字典的 code。这就是为什么你可以像列表一样拼接它。
However if possible, could you add an example of properly passing dynamically generated strings into the final quoted expression? As far as I understand, it can be done with adding one more assignment (that would add quotation), but is there a more elegant way?
Hy's models 被认为是 public API 的一部分,只是在宏之外很少使用。在需要时使用它们很好。其他 Lisp 并没有在代码模型对象和它们产生的数据之间做出同样的区分。 Hy 这样做是为了更好地 Python 互操作。有人可能会争辩说 ~
语法应该为某些数据类型自动执行此转换,但目前并没有。
[更新:在当前的 master 分支上,Hy 的编译器将在可能的情况下自动包装 Hy 模型中的兼容值,因此您通常不必再自己执行此操作。]
HySymbol
适用于像您尝试做的那样从字符串动态生成符号。这不是唯一的方法,但在这种情况下这是您想要的。另一种方式,gensym
,在宏中使用得更多,但它们不能那么漂亮。您可以使用字符串调用 gensym
以为其提供更有意义的名称以用于调试目的,但它仍然具有数字后缀以使其唯一。当然,您可以分配 HySymbol
一个较短的别名,或者将该部分委托给辅助函数。
也可以提前转换,比如片段
(def class-name (.format "{}_{}" (. meta-base __name__) ...
可以改为
(def class-name (HySymbol (.format "{}_{}" (. meta-base __name__) ...
这样就不用重复了
(setv class-cand [class-name])
(setv class-def [`(defclass ~class-name ...
这可能会使模板更易于阅读。
更新
Hy master 现在在编译时将符号分解为有效的 Python 标识符,因此 hy2py
工具和 astor 反汇编应该更可靠地生成有效的 Python 代码,即使有特殊的符号中的字符。