Evaluar la expresión dada como una cadena

283

Tengo curiosidad por saber si R puede usar su eval()función para realizar cálculos proporcionados, por ejemplo, por una cadena.

Este es un caso común:

eval("5+5")

Sin embargo, en lugar de 10 obtengo:

[1] "5+5"

¿Alguna solución?

Federico Giorgi
fuente
66
A pesar de todas las respuestas que muestran cómo resolver eso con parse ... ¿Por qué necesita almacenar tipos de idioma en un carácter string? La respuesta de Martin Mächler debería merecer muchos más votos a favor.
Petr Matousu el
77
Gracias @PetrMatousu. Sí, me sorprende ver cómo la información errónea se propaga en SO ahora ... por personas que votan por eval(parse(text = *)) soluciones falsas.
Martin Mächler
2
Quiero ejecutar scripts de la forma: QQ = c('11','12','13','21','22','23')es decir: QQ = c (..., 'ij', ..) con i, j variando en un rango que puede variar de una ejecución a otra. Para este y otros ejemplos similares, puedo escribir el script como paste( "QQ = c('", paste(rep(1:2,each=3),1:3, sep="", collapse="','"), "')",sep=""), y la opción eval(parse(text=...))crea el vector QQ en el entorno de trabajo según el script. ¿Cuál sería la forma correcta de codificador R para hacer esto, si no fuera con "text = ..."?
VictorZurkowski

Respuestas:

418

La eval()función evalúa una expresión, pero "5+5"es una cadena, no una expresión. Use parse()con text=<string>para cambiar la cadena en una expresión:

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

Llamar eval()invoca muchos comportamientos, algunos no son inmediatamente obvios:

> 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

Ver también tryCatch .

Harlan
fuente
27
Como Shane señala a continuación, "
Debe
1
Se deben especificar los efectos secundarios del uso de eval (parse). Por ejemplo, si tiene un nombre de variable predefinido igual a "David" y lo reasigna usando eval (parse (text = "name") == "Alexander", obtendrá un error porque eval & parse no devuelve un Expresión R que se puede evaluar.
Crt
1
@NelsonGon: expresiones no evaluadas construidas usando quote(), bquote()o las herramientas más sofisticadas proporcionadas por el rlangpaquete.
Artem Sokolov
@ArtemSokolov Gracias, de alguna manera sigo volviendo a esta pregunta en busca de una alternativa. Lo miré, rlangpero lo más cercano que encontré fue parse_exprqué llamadas, parse_exprsa su vez, es lo mismo que usar parsey envolver, lo evalque parece ser lo mismo que aquí. No estoy seguro de cuál sería la ventaja de usar rlang.
NelsonGon
1
@NelsonGon: con rlang, trabajarías directamente con expresiones, no con cadenas. No es necesario un paso de análisis. Tiene dos ventajas. 1. Las manipulaciones de expresiones siempre producirán expresiones válidas. Las manipulaciones de cadenas solo producirán cadenas válidas. No sabrá si son expresiones válidas hasta que las analice. 2. No existe un equivalente a la substitute()clase de funciones en el mundo de las cadenas, lo que limita severamente su capacidad para manipular las llamadas a funciones. Considere esta envoltura glm . ¿Cómo sería un equivalente de cadena?
Artem Sokolov
100

Puede usar la parse()función para convertir los caracteres en una expresión. Debe especificar que la entrada es texto, porque parse espera un archivo por defecto:

eval(parse(text="5+5"))
Shane
fuente
77
> fortunes :: fortune ("la respuesta es parse") Si la respuesta es parse (), generalmente debería repensar la pregunta. - Thomas Lumley R-help (febrero de 2005)>
Martin Mächler
13
@ MartinMächler ¡Eso es irónico, porque los paquetes principales de R se usan parsetodo el tiempo! github.com/wch/r-source/…
geneorama
49

Lo siento, pero no entiendo por qué tanta gente incluso piensa que una cadena es algo que podría evaluarse. Debes cambiar tu mentalidad, de verdad. Olvídese de todas las conexiones entre cadenas en un lado y expresiones, llamadas, evaluación en el otro lado.

La (posiblemente) única conexión es vía parse(text = ....)y todos los buenos programadores de R deben saber que rara vez es un medio eficiente o seguro para construir expresiones (o llamadas). Más bien, aprenda más sobre substitute(), quote()y posiblemente el poder de usar 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)

Dec.2017: Ok, aquí hay un ejemplo (en los comentarios, no hay un buen formato):

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

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

y si adquieres más experiencia aprenderás que q5es un "call"mientras que e5es un "expression", e incluso eso e5[[1]]es idéntico a q5:

identical(q5, e5[[1]])
# [1] TRUE
Martin Mächler
fuente
44
¿podrías dar un ejemplo? tal vez podría mostrarnos cómo "mantener" 5 + 5 en un objeto r, luego evaluarlo más tarde, usando comillas y sustitutos en lugar de un carácter y eval (parse (text =)?
Richard DiSalvo
3
Puedo estar un poco perdido. ¿En qué punto obtienes 10? ¿O ese no es el punto?
Nick S
@RichardDiSalvo: sí, q5 <- quote(5+5)arriba está la expresión (en realidad, la "llamada") 5+5y es un objeto R, pero no una cadena. Puedes evaluarlo en cualquier momento. Nuevamente: usando, quote (), substitute (), ... en su lugar, parse crea llamadas o expresiones directamente y de manera más eficiente que a través de parse (text =.). El uso eval()está bien, el uso parse(text=*)es propenso a errores y, a veces, es bastante ineficiente en comparación con las llamadas de construcción y su manipulación. @Nick S: Es eval(q5) o eval(e5) en nuestro ejemplo actual
Martin Mächler
@NickS: para obtener 10, evalúa la llamada / expresión, es decir, invoca eval(.). Mi punto era que la gente no debería usar, parse(text=.)sino más bien quote(.), etc., para construir la llamada que luego será eval()editada.
Martin Mächler
2
eval(quote())funciona en algunos casos, pero fallará en algunos casos donde eval(parse())funcionaría bien.
NelsonGon
18

Alternativamente, puede usar evalsdesde mi panderpaquete para capturar la salida y todas las advertencias, errores y otros mensajes junto con los resultados sin procesar:

> 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
fuente
2
Buena función llena un agujero dejado al evaluate::evaluatedevolver el objeto de resultado; eso deja su función adecuada para usar para llamar a través de mclapply. ¡Espero que esa característica permanezca!
russellpierce
Gracias @rpierce. Esta función se escribió originalmente en 2011 como parte de nuestro rapportpaquete, y desde entonces se ha mantenido activamente como un uso intensivo en nuestro servicio de rapporter.net , además de algunos otros proyectos, así que estoy seguro de que se mantendrá durante un tiempo. while :) Me alegra que lo encuentres útil, gracias por tus amables comentarios.
daroczig
14

Hoy en día también puedes usar la lazy_evalfunción del lazyevalpaquete.

> lazyeval::lazy_eval("5+5")
[1] 10
Paweł Kozielski-Romaneczko
fuente
2

De manera similar usando rlang:

eval(parse_expr("5+5"))
c1au61o_HH
fuente
3
Vine aquí buscando una rlangrespuesta, pero ¿qué pasa si alguna es la ventaja de esto sobre las alternativas básicas? En realidad, un examen minucioso del código utilizado muestra que, de hecho, está utilizando eval(parse(....))lo que quería evitar.
NelsonGon
44
No solo esos aspectos negativos, sino que su nombre también es engañoso. NO está evaluando una expresión. Debe llamarse parse_to_expr o algo más para indicar que el usuario sabrá que está destinado a argumentos de caracteres.
IRTFM