【问题标题】:What is the clojure equivalent of the Python idiom "if __name__ == '__main__'"?Python 习语“if __name__ == '__main__'”的 clojure 等价物是什么?
【发布时间】:2010-11-01 16:00:48
【问题描述】:

我正在涉足 clojure,但在尝试确定这个常见的 python 习语的 clojure(和/或 Lisp)等价物时遇到了一些麻烦。

习语是在一个python模块的底部通常有一点测试代码,然后是运行代码的语句,例如:

# mymodule.py
class MyClass(object):
    """Main logic / code for the library lives here"""
    pass

def _runTests():
    # Code which tests various aspects of MyClass...
    mc = MyClass() # etc...
    assert 2 + 2 == 4

if __name__ == '__main__': _runTests()

这对于简单的临时测试很有用。人们通常会通过编写from mymodule import MyClass 来使用这个模块,在这种情况下,永远不会调用_runTests(),但是在最后加上sn-p,也可以通过直接从命令行输入python mymodule.py 来运行它。

Clojure(和/或常见的 lisp)中是否有等效的成语?我不是在追求一个成熟的单元测试库(嗯,我是,但不是在这个问题中),我只想在一个模块中包含一些代码,这些代码只会在某些情况下运行,所以我可以拥有一种运行我一直在处理的代码的快速方法,但仍然允许我的文件像普通模块/命名空间一样被导入。

【问题讨论】:

    标签: python lisp clojure idioms


    【解决方案1】:

    我对 Clojure 很陌生,但我认为 Clojure 组中的 this discussion 可能是一个解决方案和/或解决方法,特别是 Stuart Sierra 于 4 月 17 日晚上 10:40 发布的帖子。

    【讨论】:

      【解决方案2】:

      您可能想看看来自 clojure-contrib 的 test-is 库。这不是同一个习惯用法,但它应该支持非常相似的工作流程。

      【讨论】:

        【解决方案3】:

        在 Common Lisp 中,您可以通过 features 使用条件阅读。

        #+testing (run-test 'is-answer-equal-42)
        

        如果绑定到cl:*features* 的功能列表将包含符号 :testing ,则只会在加载期间读取并执行上述内容。

        例如

        (let ((*features* (cons :testing *features*)))
           (load "/foo/bar/my-answerlib.lisp"))
        

        将暂时添加 :testing 到功能列表中。

        您可以定义自己的功能并控制 Common Lisp 系统读取和跳过哪些表达式。

        此外,您还可以这样做:

        #-testing (print '|we are in production mode|)
        

        【讨论】:

        • 我不认为 features 对此有好处。 features 显示可用的功能,而不是一些环境状态或运行代码的请求。
        • 为什么不呢? 特性用于各种东西:描述运行事物的硬件,一些可用的核心库,软件的一些模式,Lisp 实现的版本,语言的版本,是否它是 :production-mode 或 :development-mode 等。
        【解决方案4】:

        Common Lisp 和 Clojure(以及其他 lisp)提供了与 REPL 交互的环境,您不需要像 «if __name__ == '__main__'» 这样的技巧。 python 有类似 REPL 的环境:命令行中的 python、ipython、Emacs 的 python 模式等。

        你应该只创建库,添加一个测试套件(Common Lisp 有很多测试框架;我更喜欢5am 框架,有一个可用框架的调查here)。然后加载库,在 REPL 中,您可以对库执行任何操作:运行测试、调用函数、实验等。

        当您发现一个失败的测试时,您对其进行修复,重新编译更改的代码,然后继续试验、运行测试,而无需重新启动整个应用程序。这节省了很多时间,因为正在运行的应用程序可能已经积累了很多状态(它可能已经创建了 gui 窗口,连接到数据库,到达了一些不容易重现的关键时刻),并且您不必重新启动它每次更改后。

        这是 Common Lisp 的一个示例(来自我的 cl-sqlite 库):

        代码:

        (def-suite sqlite-suite)
        
        (defun run-all-tests ()
          (run! 'sqlite-suite));'
        
        (in-suite sqlite-suite)
        
        (test test-connect
          (with-open-database (db ":memory:")))
        
        (test test-disconnect-with-statements
          (finishes
            (with-open-database (db ":memory:")
              (prepare-statement db "create table users (id integer primary key, user_name text not null, age integer null)"))))
        ...
        

        还有互动环节:

        CL-USER> (sqlite-tests:run-all-tests)
        .......
         Did 7 checks.
            Pass: 7 (100%)
            Skip: 0 ( 0%)
            Fail: 0 ( 0%)
        
        NIL
        CL-USER> (defvar *db* (sqlite:connect ":memory:"))
        *DB*
        CL-USER> (sqlite:execute-non-query *db* "create table t1 (field text not null)")
        ; No value
        CL-USER> (sqlite:execute-non-query *db* "insert into t1 (field) values (?)" "hello")
        ; No value
        CL-USER> (sqlite:execute-to-list *db* "select * from t1")
        (("hello"))
        CL-USER> 
        

        现在假设我在 sqlite:execute-to-list 中发现了错误。我去这个函数的代码,修复错误并重新编译这个函数。然后我调用固定函数并确保它有效。内存数据库并没有消失,它与重新编译前的状态相同。

        【讨论】:

        • name__=='__main' 习语实际上与 REPL 无关 - 它是一种区分“作为模块导入”和“作为脚本运行”的方法”。其中的代码通常不是您在 REPL 中试验的简单代码和试用代码,而是您希望重复执行完全相同的代码。测试代码就是一个例子,但最常见的通常是有一个脚本,它也允许作为一个模块重用。
        • 是的,一般来说,这些是不同的东西。但是在这个问题的上下文中,检查 name 用于运行(和重新运行)测试,并且 REPL 对于 lisps 中的这种用例是惯用的。
        • 用户要求 name==main idiom,不是 repl,不是 test suite。
        【解决方案5】:

        从命令行一遍又一遍地运行 Clojure 脚本并不习惯。 REPL 是一个更好的命令行。 Clojure 是一个 Lisp,启动 Clojure 并让同一个实例永远运行并与之交互而不是重新启动它是很常见的。您可以一次更改一个正在运行的实例中的函数,运行它们并根据需要戳它们。摆脱繁琐而缓慢的传统编辑/编译/调试周期是 Lisps 的一大特色。

        您可以轻松地编写函数来执行诸如运行单元测试之类的操作,并且只要您想运行它们就可以从 REPL 中调用这些函数,否则就忽略它们。在 Clojure 中,通常使用 clojure.contrib.test-is,将测试函数添加到命名空间,然后使用 clojure.contrib.test-is/run-tests 来运行它们。

        另一个不从命令行运行 Clojure 的好理由是 JVM 的启动时间可能会令人望而却步。

        如果你真的想从命令行运行 Clojure 脚本,有很多方法可以做到。请参阅the Clojure mailing list 进行一些讨论。

        一种方法是测试是否存在命令行参数。鉴于当前目录中的 foo.clj

        (ns foo)
        
        (defn hello [x] (println "Hello," x))
        
        (if *command-line-args*
          (hello "command line")
          (hello "REPL"))
        

        根据您启动 Clojure 的方式,您将获得不同的行为。

        $ java -cp ~/path/to/clojure.jar:. clojure.main foo.clj --
        Hello, command line
        $ java -cp ~/path/to/clojure.jar:. clojure.main
        Clojure 1.1.0-alpha-SNAPSHOT
        user=> (use 'foo)
        Hello, REPL
        nil
        user=>
        

        如果您想了解其工作原理,请参阅 Clojure 源代码中的 src/clj/clojure/main.clj

        另一种方法是将您的代码编译成.class 文件并从Java 命令行调用它们。给定一个源文件foo.clj

        (ns foo
          (:gen-class))
        
        (defn hello [x] (println "Hello," x))
        
        (defn -main [] (hello "command line"))
        

        建立一个目录来存放编译后的.class文件;这默认为./classes。你必须自己创建这个文件夹,Clojure 不会创建它。还要确保将$CLASSPATH 设置为包含./classes 和包含源代码的目录;我假设foo.clj 在当前目录中。所以从命令行:

        $ mkdir classes
        $ java -cp ~/path/to/clojure.jar:./classes:. clojure.main
        Clojure 1.1.0-alpha-SNAPSHOT
        user=> (compile 'foo)
        foo
        

        classes 目录中,您现在将拥有一堆.class 文件。从命令行调用你的代码(默认运行-main函数):

        $ java -cp ~/path/to/clojure.jar:./classes foo
        Hello, command line.
        

        clojure.org 上有很多关于编译 Clojure 代码的信息。

        【讨论】:

          【解决方案6】:

          如果你说的是有一个“入口点”,你当然可以这样做:

          (ns foo)
          
          (defn foo [n]
            (inc n))
          
          (defn main []
            (println "working")
            (println "Foo has ran:" (foo 1)))
          
          (main)
          

          现在将发生的情况是,只要这段代码被 (load-file "foo.clj")'d 或 (use 'foo) 或 (require 'foo),那么 (main) 就会被调用,这通常不会完成。

          更常见的是,可以在 REPL 中加载代码文件,然后用户调用 main 函数。

          【讨论】:

          • 这是否可以通过这样一种方式来完成,即 (main) 仅在 foo.clj 直接运行时触发,而不是在另一个脚本加载它时触发?
          • 我不这么认为,因为在这两种情况下,您都会评估(然后编译)所有表达式。总是有允许定义入口点的 AOT 编译:clojure.org/compilation
          【解决方案7】:

          http://rosettacode.org/wiki/Scripted_Main#Clojure 还列出了不同的可能性。 (如果你发现了一个新的 - 请添加它。;-))

          【讨论】:

            【解决方案8】:

            Boot 是一种构建工具(leiningen 的替代品),即supports scripts。所以你可以有一个以#!/usr/bin/env boot 开头的启动脚本,它可以有一个-main 方法。

            您还可以创建从命令行调用的任务,这些任务会调用代码的不同功能。你可以有一个打包任务,可以为这些函数之一创建一个 uberjar 作为入口点。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2011-01-15
              • 2010-10-16
              • 1970-01-01
              • 2018-11-22
              相关资源
              最近更新 更多