我正在尝试从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)

定义varvvarnvara等,而不是var1var2var3。似乎可以正确调用:
(macroexpand `(make-vars ~vnames))

但这似乎过于复杂。
我遇到的另一个问题是HySymbol的必要性,这让我大吃一惊。但当我尝试第二种方法时,我真的很受伤,在第二种方法中,我创建了一个返回引用形式的函数:
(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的内部类,而我没有正确地表示真与假,而是使用HyInteger(1)HyInteger(0)来代替。
为了从这个函数中获取python代码,我通过disassemble运行它的结果。
总结一下:
从hy生成python代码的最佳方法是什么?
什么是真与假的内部表示?
可以调用一个函数来处理它的参数并从宏返回一个带引号的hy表单吗?如何调用?

最佳答案

在Hy中,通常不需要生成Python代码,因为Hy更擅长生成Hy代码,而且它也是可执行的这在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

变量名+!@$与ast中的spam一样合法,但python的exec阻塞了它,因为它不是有效的python标识符。
如果您理解并接受此限制,则可以使用disassemble,但不使用宏。普通运行时函数可以获取并生成(演示时)hy表达式。宏实际上只是这样的函数,而不是在编译时运行的在Hy中,宏将其工作的一部分委托给一个普通函数并将Hy表达式作为其参数之一并返回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表单生成的,则不必转换它们但没有理由非要在最后一分钟在回音表内完成。如果您喜欢的话,可以通过helper函数提前完成。
改善效果追踪
可以调用一个函数来处理它的参数并从宏返回一个带引号的hy表单吗?如何调用?
我的原意是宏对于你所要做的事情是错误的但要澄清的是,您可以在运行时调用宏,方法是使用macroexpand,正如您已经演示的那样当然,您可以将macroexpand调用放在另一个函数中,但是macroexpand必须有一个带引号的形式作为其参数。
另外,关于动态生成字典的问题也是一样的我用过的建筑看起来很糟糕。
字典部分可以简化为
{~@(interleave (map HyString class-grouping) (repeat '1))}

虽然python的dict由哈希表支持,但hy的HyDict模型实际上只是一个列表。这是因为它不代表哈希表本身,而是生成dict的代码。这就是为什么您可以像列表一样拼接到其中。
但是,如果可能的话,您可以添加一个将动态生成的字符串正确地传递到最终引用的表达式中的示例吗?据我所知,这可以通过增加一个作业来完成(这会增加报价),但有没有更优雅的方式呢?
Hy's models被认为是公共API的一部分,它们只是在宏之外很少使用必要时可以用。其他lisp在代码模型对象和它们生成的数据之间没有相同的区别。hy这样做是为了更好地实现python互操作。有人可能会争辩说~语法应该为某些数据类型自动进行这种转换,但目前它没有。
[更新:在当前的主分支上,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代码,即使符号中有特殊字符。

10-04 10:58