Avaliar expressão dada como uma sequência

283

Estou curioso para saber se R pode usar sua eval()função para realizar cálculos fornecidos por, por exemplo, uma string.

Este é um caso comum:

eval("5+5")

No entanto, em vez de 10, recebo:

[1] "5+5"

Qualquer solução?

Federico Giorgi
fonte
6
Apesar de todas as respostas mostrarem como resolver isso com análise ... Por que você precisa armazenar tipos de idioma em um personagem string? A resposta de Martin Mächler deve merecer muito mais votos positivos.
Petr Matousu
7
Obrigado @PetrMatousu. Sim, estou chocado ao ver como as informações erradas se espalham no SO agora ... por pessoas que votam em eval(parse(text = *)) soluções falsas.
Martin Mächler 01/04/19
2
Eu quero executar scripts do formulário:, QQ = c('11','12','13','21','22','23')ie: QQ = c (..., 'ij', ..) com i, j variando em um intervalo que pode variar de execução para execução. Para este e outros exemplos semelhantes, posso escrever o script como paste( "QQ = c('", paste(rep(1:2,each=3),1:3, sep="", collapse="','"), "')",sep="")e a opção eval(parse(text=...))cria o vetor QQ no ambiente de trabalho, conforme o script. Qual seria a maneira correta do codificador R fazer isso, se não com "text = ..."?
VictorZurkowski

Respostas:

418

A eval()função avalia uma expressão, mas "5+5"é uma sequência, não uma expressão. Use parse()with text=<string>para alterar a string em uma expressão:

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

Chamar eval()invoca muitos comportamentos, alguns não são imediatamente óbvios:

> 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

Veja também tryCatch .

Harlan
fonte
27
Como Shane observa abaixo, "Você precisa especificar que a entrada é texto, porque a análise espera um arquivo por padrão"
PatrickT
1
os efeitos colaterais do uso de eval (análise) devem ser especificados. Por exemplo, se você tiver um nome de variável predefinido igual a "David" e reatribuir usando eval (parse (text = "name") == "Alexander", você receberá um erro porque eval & parse não retornam um Expressão de R que pode ser avaliada.
Crt
1
@NelsonGon: expressões não avaliadas construído usando quote(), bquote()ou as ferramentas mais sofisticadas fornecidas pelo rlangpacote.
Artem Sokolov
@ArtemSokolov Obrigado, de alguma forma, continuo voltando a esta pergunta procurando uma alternativa. Eu olhei, rlangmas o mais próximo que encontrei foi parse_exprquais chamadas, parse_exprsque por sua vez, são iguais a usá parse-las e envolvê-las nas evalquais parece ser a mesma coisa que aqui. Não tenho certeza qual seria a vantagem de usar rlang.
NelsonGon
1
@ NelsonGon: com rlang, você trabalharia diretamente com expressões, não com strings. Nenhuma etapa de análise necessária. Tem duas vantagens. 1. Manipulações de expressão sempre produzirão expressões válidas. Manipulações de strings produzirão apenas strings válidos. Você não saberá se são expressões válidas até que você as analise. 2. Não há equivalente à substitute()classe de funções no mundo das cordas, o que limita severamente sua capacidade de manipular chamadas de função. Considere este invólucro glm . Como seria uma string equivalente?
Artem Sokolov
100

Você pode usar a parse()função para converter os caracteres em uma expressão. Você precisa especificar que a entrada é texto, porque a análise espera um arquivo por padrão:

eval(parse(text="5+5"))
Shane
fonte
7
> fortunas :: fortuna ("resposta é análise") Se a resposta for análise (), você deve repensar a pergunta. - Thomas Lumley R-help (fevereiro de 2005)>
Martin Mächler
13
@ MartinMächler Isso é irônico, porque os principais pacotes R usam parseo tempo todo! github.com/wch/r-source/…
geneorama 30/03
49

Desculpe, mas não entendo por que muitas pessoas acham que uma string é algo que pode ser avaliado. Você deve mudar sua mentalidade, realmente. Esqueça todas as conexões entre seqüências de caracteres de um lado e expressões, chamadas e avaliação do outro lado.

A (possivelmente) única conexão é via parse(text = ....)e todos os bons programadores de R devem saber que esse raramente é um meio eficiente ou seguro para construir expressões (ou chamadas). Em vez disso, aprenda mais sobre substitute(), quote()e possivelmente o 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)

Dez.2017: Ok, aqui está um exemplo (nos comentários, não há formatação legal):

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

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

e se você tiver mais experiência, aprenderá que q5é um "call"considerando e5é um "expression", e até isso e5[[1]]é idêntico a q5:

identical(q5, e5[[1]])
# [1] TRUE
Martin Mächler
fonte
4
Você poderia dar um exemplo? talvez você poderia nos mostrar como "hold on" para 5 + 5 em um objeto r, então avaliá-lo mais tarde, usando cotação e substituto em vez de um personagem e eval (parse (text =)?
Richard DiSalvo
3
Eu posso estar um pouco perdido. Em que ponto você recebe 10? Ou não é esse o ponto?
Nick S
@RichardDiSalvo: sim, q5 <- quote(5+5)acima está a expressão (na verdade, a "chamada") 5+5e é um objeto R, mas não uma string. Você pode avaliar a qualquer momento. Novamente: using, quote (), substitute (), ... em vez disso análise cria chamadas ou expressões direta e com mais eficiência do que via análise (text =.). Usando eval()é fina, usando parse(text=*)é propenso a erros e por vezes bastante ineficiente em comparação com chamadas de construção e manipulá-los .. @ Nick S: É eval(q5) ou eval(e5) em nosso exemplo de execução
Martin Machler
@ Nicks: para obter 10, você avalia a chamada / expressão, ou seja, solicita eval(.)-a. Meu argumento era que as pessoas não deveriam usar, parse(text=.)mas sim quote(.)etc, para construir a chamada que mais tarde será eval()editada.
Martin Mächler 6/08/19
2
eval(quote())funciona em alguns casos, mas falha em alguns casos em eval(parse())que funcionaria bem.
NelsonGon
18

Como alternativa, você pode usar evalsdo meu panderpacote para capturar a saída e todos os avisos, erros e outras mensagens junto com os resultados brutos:

> 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
fonte
2
Boa função; preenche um buraco deixado evaluate::evaluateretornando o objeto de resultado; que deixa sua função adequada para uso por chamada via mclapply. Espero que esse recurso permaneça!
russellpierce
Obrigado, @rpierce. Essa função foi originalmente escrita em 2011 como parte de nosso rapportpacote e foi ativamente mantida desde então como sendo muito usada em nosso serviço rapporter.net além de alguns outros projetos - por isso, tenho certeza de que ela permanecerá por um período. while :) Fico feliz que você ache útil, obrigado pelo seu feedback.
daroczig
14

Atualmente você também pode usar a lazy_evalfunção do lazyevalpacote.

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

Da mesma forma, usando rlang:

eval(parse_expr("5+5"))
c1au61o_HH
fonte
3
Vim aqui à procura de uma rlangresposta, mas e se houver, é a vantagem disso sobre as alternativas básicas? Na verdade, um exame atento do código usado mostra que ele está de fato usando o eval(parse(....))que eu queria evitar.
NelsonGon
4
Não apenas esses negativos, mas seu nome também é enganador. NÃO está avaliando uma expressão. Deve ser chamado parse_to_expr ot outra coisa para indicar que o usuário saberá que ele se destina a argumentos de caracteres.
IRTFM