Оценить выражение в виде строки

283

Мне любопытно узнать, может ли R использовать свою eval()функцию для выполнения вычислений, предоставляемых, например, строкой.

Это общий случай:

eval("5+5")

Однако вместо 10 я получаю:

[1] "5+5"

Любое решение?

Федерико Георгий
источник
6
Несмотря на все ответы, показывающие, как решить эту проблему с помощью синтаксического анализа ... Зачем вам нужно хранить языковые типы в символе string? Ответ Мартина Мехлера заслуживает гораздо большего числа голосов.
Петр Матоусу
7
Спасибо @PetrMatousu. Да, я потрясен, увидев, как ложная информация распространяется на SO сейчас ... людьми, которые голосуют за eval(parse(text = *)) поддельные решения.
Мартин Мехлер
2
Я хочу запускать сценарии в форме:, QQ = c('11','12','13','21','22','23')то есть: QQ = c (..., 'ij', ..) с i, j, варьирующимся в диапазоне, который может варьироваться от запуска к запуску. Для этого и подобных примеров я могу написать сценарий как paste( "QQ = c('", paste(rep(1:2,each=3),1:3, sep="", collapse="','"), "')",sep=""), а опция eval(parse(text=...))создает вектор QQ в рабочей среде согласно сценарию. Каков будет правильный способ кодера R сделать это, если не с "text = ..."?
ВикторЗурковский,

Ответы:

419

eval()Функция вычисляет выражение, но "5+5"это строка, а не выражение. Используйте parse()с, text=<string>чтобы изменить строку в выражение:

> eval(parse(text="5+5"))
[1] 10
> class("5+5")
[1] "character"
> class(parse(text="5+5"))
[1] "expression"

Вызов eval()вызывает много поведений, некоторые не сразу очевидны:

> class(eval(parse(text="5+5")))
[1] "numeric"
> class(eval(parse(text="gray")))
[1] "function"
> class(eval(parse(text="blue")))
Error in eval(expr, envir, enclos) : object 'blue' not found

Смотрите также tryCatch .

Харлан
источник
27
Как отмечает Шейн ниже: «Вы должны указать, что ввод является текстом, потому что синтаксический анализ ожидает файл по умолчанию»
PatrickT
1
побочные эффекты использования eval (parse) должны быть указаны. Например, если у вас есть предопределенное имя переменной, равное «David», и вы переназначаете его, используя eval (parse (text = "name") == "Alexander", вы получите ошибку, потому что eval & parse не возвращают Выражение R, которое можно оценить.
Crt
1
@NelsonGon: невычисленного выражения построены с использованием quote(), bquote()или более сложные инструменты , предоставляемые rlangпакетом.
Артем Соколов
@ArtemSokolov Спасибо, я как-то продолжаю возвращаться к этому вопросу в поисках альтернативы. Я посмотрел, rlangно самое близкое, что я нашел, было то, parse_exprкакие вызовы, parse_exprsв свою очередь, такие же, как использование parseи обертывание, в evalкоторых, кажется, то же самое, что и здесь. Я не уверен, какое преимущество будет иметь использование rlang.
НельсонГон
1
@NelsonGon: с rlang, вы бы работали напрямую с выражениями, а не со строками. Нет необходимости разбирать шаг. У этого есть два преимущества. 1. Манипуляции с выражениями всегда будут давать правильные выражения. Манипуляции со строками будут производить только допустимые строки. Вы не будете знать, являются ли они действительными выражениями, пока не проанализируете их. 2. В substitute()мире строк нет эквивалента классу функций, который сильно ограничивает вашу способность манипулировать вызовами функций. Посмотрите на эту упаковку GLM . Как будет выглядеть строковый эквивалент?
Артем Соколов
100

Вы можете использовать parse()функцию для преобразования символов в выражение. Вы должны указать, что входные данные являются текстовыми, потому что parse ожидает файл по умолчанию:

eval(parse(text="5+5"))
Шейн
источник
7
> fortunes :: fortune ("answer is parse") Если ответ - parse (), обычно вы должны переосмыслить вопрос. - Томас Ламли R-help (февраль 2005 г.)>
Мартин Мехлер
13
@ MartinMächler Это иронично, потому что пакеты core R используются parseпостоянно! github.com/wch/r-source/...
geneorama
49

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

Соединение (возможно) только через, parse(text = ....)и все хорошие программисты на R должны знать, что это редко является эффективным или безопасным средством для создания выражений (или вызовов). Скорее узнать больше о substitute(), quote()и , возможно , власть использования do.call(substitute, ......).

fortunes::fortune("answer is parse")
# If the answer is parse() you should usually rethink the question.
#    -- Thomas Lumley
#       R-help (February 2005)

Декабрь 2017: Хорошо, вот пример (в комментариях нет хорошего форматирования):

q5 <- quote(5+5)
str(q5)
# language 5 + 5

e5 <- expression(5+5)
str(e5)
# expression(5 + 5)

и если вы получите больше опыта вы узнаете , что q5это в "call"то время как e5это "expression", и даже то , что e5[[1]]идентично q5:

identical(q5, e5[[1]])
# [1] TRUE
Мартин Мехлер
источник
4
не могли бы вы привести пример? может быть, вы могли бы показать нам, как «удерживать» 5 + 5 в r-объекте, а затем оценить его позже, используя кавычки и подстановки, а не символ и eval (parse (text =)?
Richard DiSalvo
3
Я могу быть немного потерян. В какой момент вы получаете 10? Или это не главное?
Ник С
@RichardDiSalvo: да, q5 <- quote(5+5)выше это выражение (на самом деле "call"), 5+5и это объект R, но не строка. Вы можете оценить это в любое время. Снова: использование, quote (), substitute (), ... вместо этого parse создает вызовы или выражения напрямую и более эффективно, чем через parse (text =.). Использование eval()в порядке, использование parse(text=*)подвержено ошибкам и иногда совершенно неэффективно по сравнению с вызовами конструкции и манипулированием ими .. @ Ник С: Это eval(q5) или eval(e5) в нашем работающем примере
Мартин Мехлер
@NickS: чтобы получить 10, вы оцениваете вызов / выражение, т. Е. Вызываете eval(.)его. Моя точка зрения заключалась в том, что люди не должны использовать, parse(text=.)а, скорее, и quote(.)т.д., чтобы создать вызов, который позже будет eval()редактироваться.
Мартин Мехлер
2
eval(quote())работает в нескольких случаях, но не получится в некоторых случаях, когда eval(parse())будет работать хорошо.
НельсонГон
18

Кроме того, вы можете использовать evalsиз моего panderпакета, чтобы захватить вывод и все предупреждения, ошибки и другие сообщения вместе с необработанными результатами:

> pander::evals("5+5")
[[1]]
$src
[1] "5 + 5"

$result
[1] 10

$output
[1] "[1] 10"

$type
[1] "numeric"

$msg
$msg$messages
NULL

$msg$warnings
NULL

$msg$errors
NULL


$stdout
NULL

attr(,"class")
[1] "evals"
daroczig
источник
2
Хорошая функция; заполняет оставленное отверстие evaluate::evaluate, фактически возвращая объект результата; что делает вашу функцию пригодной для использования через mclapply. Я надеюсь, что эта функция останется!
Расселпирс
Спасибо, @rpierce. Эта функция была изначально написана в 2011 году как часть нашего rapportпакета, и с тех пор активно поддерживается как интенсивно используемая в нашем сервисе rapporter.net, помимо нескольких других проектов - так что я уверен, что она будет сохраняться в течение некоторого времени. пока :) Я рад, что вы нашли это полезным, спасибо за ваши добрые отзывы.
Дароциг
14

В настоящее время вы также можете использовать lazy_evalфункцию из lazyevalпакета.

> lazyeval::lazy_eval("5+5")
[1] 10
Павел Козельский-Романечко
источник
2

Аналогично используя rlang:

eval(parse_expr("5+5"))
c1au61o_HH
источник
3
Пришел сюда в поисках rlangответа, но что если есть преимущество в этом по сравнению с базовыми альтернативами? На самом деле, тщательное изучение используемого кода показывает, что он на самом деле использует то, eval(parse(....))чего я хотел избежать.
НельсонГон
4
Не только эти негативы, но и его название также вводит в заблуждение. Это НЕ оценка выражения. Должен называться parse_to_expr или как-то еще, чтобы указать, что пользователь будет знать, что он предназначен для символьных аргументов.
IRTFM