5

我正在尝试从 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,这是一个很大的惊喜。但是当我尝试第二种方法时,我真的受到了伤害,我创建了一个返回引用形式的函数:

(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)and 。HyInteger(0)

为了从此函数中获取 python 代码,我通过disassemble.

总结一下:

  1. 从 Hy 生成 python 代码的最佳方法是什么?
  2. True 和 False 的内部表示是什么?
  3. 可以调用一个处理其参数并从宏返回带引号的 Hy 形式的函数吗?如何?
4

1 回答 1

6

在 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,但 Pythonexec对此感到窒息,因为它不是有效的 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在内部只是一个符号。请注意,我能够生成一个HySymbolwith just ',而不使用HySymbol调用。如果您的元数据文件是用 Hy 编写的,并且首先使用引用的 Hy 形式制作,则您不必转换它们。但是没有理由必须在反引号表格内的最后一分钟完成。如果您愿意,可以通过辅助函数提前完成。


跟进

可以调用一个处理其参数并从宏返回带引号的 Hy 形式的函数吗?如何?

我最初的观点是,宏对于您正在尝试做的事情来说是错误的工具。但是为了澄清,您可以在运行时调用宏,使用macroexpand,正如您已经演示的那样。当然,您可以将macroexpand调用放在另一个函数中,但macroexpand必须使用带引号的形式作为其参数。

此外,关于动态生成的字典的相同问题。我用过的建筑看起来很糟糕。

字典部分可以简化为更像

{~@(interleave (map HyString class-grouping) (repeat '1))}

虽然 Pythondict由哈希表支持,但 Hy 的HyDict模型实际上只是一个列表。这是因为它不代表哈希表本身,而是生成字典的代码。这就是为什么你可以像列表一样拼接进去。

但是,如果可能的话,您能否添加一个将动态生成的字符串正确传递到最终引用表达式的示例?据我了解,可以通过再添加一个作业(即添加引号)来完成,但有没有更优雅的方法?

Hy 的模型被认为是公共 API 的一部分,它们只是在宏之外没有太多使用。在需要时使用它们很好。其他 Lisps 不会在代码模型对象和它们产生的数据之间做出同样的区分。Hy 这样做是为了获得更好的 Python 互操作性。有人可能会争辩说,~语法应该为某些数据类型自动进行这种转换,但目前还没有。[更新:在当前的 master 分支上,Hy 的编译器会在可能的情况下将兼容的值自动包装到 Hy 模型中,因此您通常不必再自己这样做了。]

HySymbol适用于从字符串动态生成符号,就像您尝试做的那样。这不是唯一的方法,但在这种情况下,这是您想要的。另一种方式,gensym,在宏中更常用,但它们不能那么漂亮。您可以gensym使用字符串进行调用,以便为调试目的赋予更有意义的名称,但它仍然具有数字后缀以使其唯一。当然,您可以分配HySymbol一个较短的别名,或者将该部分委托给辅助函数。

也可以提前转换,比如fragment

(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 代码,即使符号中有特殊字符也是如此。

于 2017-06-03T02:40:02.637 回答