Что такое закрытый эквивалент идиомы Python "if __name__ == '__main__'"?
Я балуюсь clojure и испытываю небольшие затруднения, пытаясь определить clojure (и / или Lisp) эквивалент этого общего языка Python.
Идиома состоит в том, что в нижней части модуля 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()
никогда не вызывается, но с фрагментом в конце его также можно запустить, набрав python mymodule.py
прямо из командной строки.
Есть ли эквивалентная идиома в Clojure (и / или общий шепот)? Я не за полноценной библиотекой модульного тестирования (да, но не в этом вопросе), я просто хотел бы включить некоторый код в модуль, который будет запускаться только при определенных обстоятельствах, так что я могу иметь быстрый способ запуска кода, над которым я работал, но все же позволяющий импортировать мой файл как обычный модуль / пространство имен.
8 ответов
Нет смысла запускать сценарии Clojure снова и снова из командной строки. REPL - лучшая командная строка. Clojure - это Лисп, обычно запускают Clojure и оставляют один и тот же экземпляр работать вечно и взаимодействуют с ним, а не перезапускают его. Вы можете изменять функции в запущенном экземпляре по одной, запускать их и тыкать их по мере необходимости. Выход из утомительного и медленного традиционного цикла редактирования / компиляции / отладки - отличная особенность Лиспса.
Вы можете легко написать функции для выполнения таких вещей, как запуск модульных тестов, и просто вызывать эти функции из REPL всякий раз, когда вы хотите запустить их, и игнорировать их в противном случае. В Clojure распространено использование clojure.contrib.test-is
, добавьте свои тестовые функции в ваше пространство имен, затем используйте clojure.contrib.test-is/run-tests
запустить их всех.
Еще одна веская причина не запускать Clojure из командной строки в том, что время запуска JVM может быть непомерно большим.
Если вы действительно хотите запустить скрипт Clojure из командной строки, есть несколько способов сделать это. Посмотрите список рассылки Clojure для некоторого обсуждения.
Один из способов - проверить наличие аргументов командной строки. Учитывая это 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=>
Увидеть src/clj/clojure/main.clj
в источнике Clojure, если вы хотите увидеть, как это работает.
Другой способ - скомпилировать ваш код в .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.
Я очень новичок в Clojure, но я думаю, что это обсуждение групп Clojure может быть решением и / или обходным путем, особенно пост Стюарта Сьерры 17 апреля в 22:40.
Также есть список различных возможностей на http://rosettacode.org/wiki/Scripted_Main. (Если вы найдете новый - пожалуйста, добавьте его.;-))
В Common Lisp вы можете использовать условное чтение с функциями.
#+testing (run-test 'is-answer-equal-42)
Выше будет считываться и, следовательно, выполняться во время загрузки, если список функций, связанных с cl:*features*, будет содержать символ:testing .
Например
(let ((*features* (cons :testing *features*)))
(load "/foo/bar/my-answerlib.lisp"))
временно добавит: тестирование в список возможностей.
Вы можете определить свои собственные функции и контролировать, какие выражения читает система Common Lisp, а какие пропускает.
Дополнительно вы также можете сделать:
#-testing (print '|we are in production mode|)
Common Lisp и Clojure (как и другие списки) предоставляют интерактивную среду с REPL, и вам не нужны такие трюки, как " if __name__ == '__main__'
". Для python существуют среды, похожие на REPL: python из командной строки, ipython, режим python для Emacs и т. Д.
Вам просто нужно создать библиотеку, добавить к ней набор тестов (для Common Lisp существует множество фреймворков для тестирования; я предпочитаю фреймворк 5 утра, здесь есть обзор фреймворков). Затем вы загружаете библиотеку, и в REPL вы можете делать с библиотекой все, что угодно: запускать тесты, вызывать функции, экспериментировать и т. Д.
Когда вы найдете провальный тест, вы исправляете его, перекомпилируете измененный код и продолжаете экспериментировать, выполняя тесты без перезапуска всего приложения. Это экономит много времени, потому что запущенное приложение могло накопить много состояний (оно могло создать графические окна, подключенные к базам данных, достигло некоторого критического момента, который нелегко воспроизвести), и вам не нужно перезапускать его после каждого изменения.
Вот пример для 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. Я иду к коду этой функции, исправляю ошибку и перекомпилирую эту функцию. Затем я вызываю фиксированную функцию и проверяю, что она работает. База данных в памяти не исчезла, она имеет то же состояние, что и до перекомпиляции.
Возможно, вы захотите взглянуть на библиотеку test-is из clojure-contrib. Это не та же идиома, но она должна поддерживать довольно похожий рабочий процесс.
Boot - это инструмент для сборки (альтернатива leiningen), который поддерживает скрипты. Таким образом, вы можете иметь загрузочный скрипт, начинающийся с #!/usr/bin/env boot
это может иметь -main
метод.
Вы также можете создавать задачи, вызываемые из командной строки, которые будут вызывать различные функции вашего кода. И у вас может быть задача упаковки, которая может создать uberjar для одной из этих функций в качестве точек входа.
Если вы говорите о "точке входа", вы, безусловно, можете сделать это:
(ns foo)
(defn foo [n]
(inc n))
(defn main []
(println "working")
(println "Foo has ran:" (foo 1)))
(main)
что произойдет сейчас, так это то, что каждый раз, когда этот код (load-file "foo.clj") 'd или (использует' foo) или (требует 'foo), будет вызван (main), что обычно не делается.
Гораздо более распространенным является то, что файл кода может быть загружен в REPL, и тогда основная функция будет вызываться пользователем.