Что такое закрытый эквивалент языка Python "if __name__ == '__main__'"? - PullRequest
25 голосов
/ 10 июня 2009

Я балуюсь 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 ]

27 голосов
/ 10 июня 2009

Нет смысла запускать сценарии 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 на clojure.org .

1 голос
/ 19 мая 2011

Существует также список различных возможностей на http://rosettacode.org/wiki/Scripted_Main#Clojure. (Если вы найдете новый - пожалуйста, добавьте его.; -))

1 голос
/ 10 июня 2009

В 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|)
1 голос
/ 10 июня 2009

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

0 голосов
/ 10 февраля 2016

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

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

0 голосов
/ 18 июня 2009

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

(ns foo)

(defn foo [n]
  (inc n))

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

(main)

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

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

0 голосов
/ 10 июня 2009

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

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

0 голосов
/ 10 июня 2009

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

...