2009-06-10 4 views
24

Я болтаю в clojure и испытываю небольшую проблему, пытаясь определить эквивалент clojure (и/или Lisp) этой общей идиомы python.Что такое clojure-эквивалент идиомы Python «если __name__ == '__main__?»?

идиома, что в нижней части модуля питона есть часто немного тестовый код, а затем оператор, который запускает код, например:

# 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() 

Это полезно для простого, объявления -hoc-тестирование. Обычно этот модуль можно использовать, записывая from mymodule import MyClass, и в этом случае _runTests() никогда не вызывается, но с фрагментом в конце можно также запустить его, введя python mymodule.py непосредственно из командной строки.

Есть ли эквивалентная идиома в Clojure (и/или распространенный lisp)? Я не после полномасштабной библиотеки тестирования модулей (ну, я, но не в этом вопросе), я просто хотел бы включить некоторый код в модуль, который будет запускаться только при некоторых обстоятельствах, поэтому я могу быстрый способ запустить код, над которым я работал, но все же разрешить импортировать мой файл, как обычный модуль/пространство имен.

ответ

27

Это не идиоматический запуск сценариев 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=> 

См 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 коды на clojure.org.

1

Я очень новичок в Clojure, но думаю, что this discussion на группах Clojure может быть решением и/или обходным решением, в частности, сообщением Stuart Sierra 17 апреля в 22:40.

0

Возможно, вы захотите ознакомиться с библиотекой test-is от clojure-contrib. Это не та же идиома, но она должна поддерживать довольно похожий рабочий процесс.

1

В Common Lisp вы можете использовать условное чтение с features.

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

Выше будет читать и, таким образом, выполнить во время загрузки, если список функций связан с cl:*features* будет содержать символ только: тестирование.

Например

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

будет временно добавить: тестирование в список функций.

Вы можете определить свои собственные функции и управлять выражениями, которые система Common Lisp читает и пропускает.

Кроме того, вы также можете сделать:

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

Я не думаю, что * Особенности * хороши для этого. * функции * показывают доступные функции, а не какое-либо состояние среды или запрос на запуск кода. –

+0

почему бы и нет? * особенности *, используемые для всех видов вещей: для описания аппаратного обеспечения, на котором работают, некоторые основные доступные библиотеки, некоторые режимы работы программного обеспечения, версия реализации Lisp, версия языка, будь то: mode или: режим разработки и т. д. –

0

Common Lisp и Clojure (а также другие лепечет) обеспечивают интерактивную среду с REPL, и вам не нужны трюки, как «if __name__ == '__main__'». Для python существуют среды, подобные REPL: питон из командной строки, ipython, режим python для Emacs и т. Д.

Вы должны просто создать библиотеку, добавить к ней testuite (существует множество тестовых фреймворков для Common Lisp , Я предпочитаю раму 5am, существует обзор доступных рамок here). Затем вы загружаете библиотеку, а в REPL вы можете делать что-либо с библиотекой: запускать тесты, функции вызова, эксперимент и т. Д.

Когда вы обнаружите неудачный тест, вы исправите его, перекомпилируйте измененный кода и продолжить эксперименты, выполняя тесты без перезапуска всего приложения.Это экономит много времени, потому что запущенное приложение может накапливать много состояний (возможно, оно создало окна gui, связанные с базами данных, достигло некоторого критического момента, который нелегко воспроизвести), и вам не нужно его перезапускать после каждого изменения.

Вот пример для Common Lisp (с моей кл-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: выполнение в список. Я перехожу к коду этой функции, исправляю ошибку и перекомпилирую эту функцию. Затем я вызываю фиксированную функцию и гарантирую, что она работает. База данных в памяти не исчезает, она имеет то же состояние, что и перед перекомпиляцией.

+3

Идиома __name __ == '__ main__' действительно не имеет отношения к REPL - ее метод различения «импортируется как модуль» и «запускается как сценарий». Код в нем, как правило, не является кодом noodling и пробным кодом, который вы экспериментируете с REPL, но кодом, который вы хотите выполнить точно так же многократно. Тестовый код является одним из примеров, но наиболее распространенным обычно является сценарий, который также позволяет повторно использовать его в качестве модуля. – Brian

+0

Да, в общем, это разные вещи. Но в контексте этого вопроса проверка __name__ была использована для запуска (и повторного запуска) тестов, а REPL - идиоматический для такого случая использования в lisps. –

+0

Пользователь попросил имя == главная идиома, а не замена, а не набор тестов. – mcandre

-3

Если говорить о том, в «точку входа» можно, конечно, сделать это:

(ns foo) 

(defn foo [n] 
    (inc n)) 

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

(main) 

, что теперь будет в том, что какое-то время этот код (нагрузка-файл «foo.clj»)» d или (используйте 'foo) или (require' foo), тогда (main) вызывается, что обычно не выполняется.

Гораздо более распространенным является то, что файл кода может быть загружен в REPL, а затем основная функция будет вызываться пользователем.

+0

Можно ли это сделать так, что (main) запускается только тогда, когда foo.clj запускается напрямую, а не когда другой скрипт загружает его? – mcandre

+0

Я так не думаю, потому что в обоих случаях вы будете оценивать (а затем компилировать) все выражения. Всегда есть компиляция AOT, которая позволяет определить точку входа: http://clojure.org/compilation – Chris

0

Boot - это инструмент для сборки (альтернатива leiningen), который supports scripts. Таким образом, у вас может быть сценарий загрузки, начинающийся с #!/usr/bin/env boot, который может иметь метод -main.

Вы также можете выполнять задачи, вызванные из командной строки, которые вызывают различные функции вашего кода. И у вас может быть задача упаковки, которая может создать uberjar для одной из этих функций в качестве точек входа.

Смежные вопросы