Оцініть вираз, поданий у вигляді рядка


283

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

Це звичайний випадок:

eval("5+5")

Однак замість 10 я отримую:

[1] "5+5"

Будь-яке рішення?


6
Незважаючи на всі відповіді, що показують, як вирішити це за допомогою розбору ... Чому потрібно зберігати мовні типи в символі string? Відповідь Мартіна Мехлера має заслуговувати на набагато більше результатів.
Петро Матусу

7
Дякую @PetrMatousu Так, я в шоці, коли спостерігають, як неправильна інформація розповсюджується на ПЗ зараз .. люди, які підтримують 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-кодер для цього, якби не текст "..."?
VictorZurkowski

Відповіді:


418

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 (синтаксичного аналізу). Наприклад, якщо у вас є заздалегідь визначене ім'я змінної, рівне "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()функцію для перетворення символів у вираз. Вам потрібно вказати, що вхід - це текст, оскільки розбір очікує файл за замовчуванням:

eval(parse(text="5+5"))

7
> fortunes :: fortune ("відповідь розбирається") Якщо відповідь розбирається (), зазвичай слід переосмислити питання. - Thomas Lumley R-help (лютий 2005 р.)>
Мартін Махлер

13
@ MartinMächler Це іронічно, адже основні пакети 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 (розбір (текст =)?
Річард Дісальво,

3
Я, можливо, трохи загубився. У який момент ви отримуєте 10? Або це не суть?
Нік S

@RichardDiSalvo: так, q5 <- quote(5+5)вище - це вираз (насправді "виклик"), 5+5і це об'єкт R, але не рядок. Ви можете це оцінити будь-коли. Знову ж таки: використання, quote (), substitute (), ... замість розбору створює виклики чи вирази безпосередньо та ефективніше, ніж через синтаксичний аналіз (text =.). Використання eval()прекрасно, використання parse(text=*)схильних до помилок, а іноді і досить неефективних порівняно з будівельними дзвінками та маніпулюванням ними. @ Nick S: Це eval(q5) або eval(e5) у нашому запущеному прикладі
Martin Mächler

@ 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"

2
Приємна функція; заповнює дірку, залишену evaluate::evaluateфактично поверненням результату; що залишає вашу функцію придатною для використання для дзвінків через mclapply. Я сподіваюся, що ця функція залишається!
russellpierce

Дякую, @rpierce. Ця функція була спочатку написана в 2011 році як частина нашого rapportпакету, і з тих пір активно підтримується як сервіс, який активно використовується в нашій службі rapporter.net, окрім кількох інших проектів. поки :) Я радий, що Ви вважаєте це корисним, дякую за добрі відгуки.
daroczig


2

Аналогічно використовуючи rlang:

eval(parse_expr("5+5"))

3
Прийшов сюди, шукаючи rlangвідповідь, але що, якщо є якась перевага цього перед базовими альтернативами? Власне, ретельне вивчення використовуваного коду показує, що насправді це використання, eval(parse(....))якого я хотів уникнути.
НельсонГон

4
Не тільки ці негативи, але і його назва також вводить в оману. Це НЕ оцінювання виразу. Слід назвати parse_to_expr від чогось іншого, щоб вказати, що користувач буде знати, що він призначений для аргументів символів.
IRTFM
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.