Expressão vs Declaração em Python: Qual é a diferença?
Depois de trabalhar com Python por um tempo, você eventualmente encontrará dois termos aparentemente semelhantes: expressão e instrução. Ao navegar na documentação oficial ou pesquisar um tópico relacionado ao Python em um fórum on-line, você pode ter a impressão de que as pessoas usam esses termos de forma intercambiável. Isso geralmente é verdade, mas, para confusão suficiente, há casos em que a distinção entre expressão e afirmação se torna importante.
Então, qual é a diferença entre expressões e declarações em Python?
Resumindo: expressões têm valores e declarações causam efeitos colaterais
Ao abrir o glossário Python, você encontrará as duas definições a seguir:
Expressão: uma parte da sintaxe que pode ser avaliada para algum valor. (…) (Fonte)
Instrução: uma instrução faz parte de um conjunto (um “bloco” de código). Uma declaração é uma expressão ou uma das várias construções com uma palavra-chave, (…) (Fonte)
Bem, isso não é particularmente útil, é? Felizmente, você pode resumir os fatos mais importantes sobre expressões e declarações em menos de três pontos:
- Todas as instruções em Python se enquadram na ampla categoria de declarações.
- Por esta definição, todas as expressões também são declarações – às vezes chamadas de declarações de expressão.
- Nem toda afirmação é uma expressão.
Em um sentido técnico, toda linha ou bloco de código é uma declaração em Python. Isso inclui expressões , que representam um tipo especial de afirmação. O que torna uma expressão especial? Você descobrirá agora.
Expressões: declarações com valores
Essencialmente, você pode substituir todas as expressões do seu código pelos valores computados, que elas produziriam em tempo de execução, sem alterar o comportamento geral do seu programa. As declarações, por outro lado, não podem ser substituídas por valores equivalentes, a menos que sejam expressões.
Considere o seguinte snippet de código:
>>> x = 42
>>> y = x + 8
>>> print(y)
50
Neste exemplo, todas as três linhas de código contêm instruções. As duas primeiras são instruções de atribuição, enquanto a terceira é uma chamada à função print()
.
Ao observar cada linha mais de perto, você pode começar a desmontar a instrução correspondente em subcomponentes. Por exemplo, o operador de atribuição (=
) consiste nas partes à esquerda e à direita. A parte à esquerda do sinal de igual indica o nome da variável, como x
ou y
, e a parte à direita é o valor atribuído a essa variável.
A palavra valor é a chave aqui. Observe que à variável x
é atribuído um valor literal, 42
, que está embutido em seu código. Por outro lado, a linha a seguir atribui uma expressão aritmética, x + 8
, à variável y
. Python deve primeiro calcular ou avaliar tal expressão para determinar o valor final da variável quando seu programa estiver em execução.
Expressões aritméticas são apenas um exemplo de expressões de Python. Outros incluem expressões lógicas, expressões condicionais e muito mais. O que todos eles têm em comum é um valor para o qual avaliam, embora cada valor geralmente seja diferente. Como resultado, você pode substituir com segurança qualquer expressão pelo valor correspondente:
>>> x = 42
>>> y = 50
>>> print(y)
50
Este pequeno programa dá o mesmo resultado de antes e é funcionalmente idêntico ao anterior. Você calculou a expressão aritmética manualmente e inseriu o valor resultante em seu lugar.
Observe que você pode avaliar x + 8
, mas não pode fazer o mesmo com a atribuição y=x + 8
, mesmo que ela incorpore uma expressão. Toda a linha de código representa uma instrução pura sem valor intrínseco. Então, qual é o sentido de ter tais declarações? É hora de mergulhar nas instruções Python e descobrir.
Declarações: instruções com efeitos colaterais
As declarações que não são expressões causam efeitos colaterais, que alteram o estado do seu programa ou afetam um recurso externo, como um arquivo no disco. Por exemplo, quando você atribui um valor a uma variável, você define ou redefina essa variável em algum lugar da memória do Python. Da mesma forma, quando você chama print()
, você grava efetivamente no fluxo de saída padrão (STDOUT), que, por padrão, exibe o texto na tela.
Observação: embora as declarações incluam expressões, a maioria das pessoas usa a palavra declaração informalmente quando se referem a declarações puras ou instruções sem valor.
OK. Você cobriu declarações que são expressões e declarações que não são expressões. De agora em diante, você pode se referir a elas como expressões puras e declarações puras, respectivamente. Mas acontece que há um meio-termo aqui.
Algumas instruções podem ter um valor e causar efeitos colaterais ao mesmo tempo. Em outras palavras, eles são expressões com efeitos colaterais ou, equivalentemente, declarações com um valor. Um excelente exemplo disso seria a função
do Python, incorporada ao idioma:
>>> fruit = iter(["apple", "banana", "orange"])
>>> next(fruit)
'apple'
>>> next(fruit)
'banana'
>>> next(fruit)
'orange'
Aqui, você define um objeto iterador chamado fruit
, que permite iterar uma lista de nomes de frutas. Cada vez que você chama next()
neste objeto, você modifica seu estado interno movendo o iterador para o próximo item na lista. Esse é o seu efeito colateral. Simultaneamente, next()
retorna o nome da fruta correspondente, que é a parte do valor da equação.
Em geral, é considerado prática recomendada não misturar valores com efeitos colaterais em uma única expressão. A programação funcional incentiva o uso de funções puras, enquanto os idiomas processuais fazem uma distinção entre as funções que retornam um valor e procedimentos que não.
Nota: No Python, todas as funções retornam um valor. Mesmo quando uma de suas funções não envolve a instrução retornar
, o Python faz com que a função retorne implicitamente Nenhum
.
Por fim, embora isso possa parecer contra -intuitivo no início, o Python tem uma declaração que não avalia nada nem causa efeitos colaterais. Você consegue adivinhar o que é e quando deseja usá -lo? Para lhe dar uma pequena dica, é comumente conhecido como um não-Op na ciência da computação, que é abreviada para nenhuma operação .
Adivinha? É a instrução pass
do Python! Você costuma usá-lo como espaço reservado em locais onde uma instrução é sintaticamente necessária, mas não deseja realizar nenhuma ação. Você pode usar esses espaços reservados em definições de funções vazias ou loops durante os estágios iniciais de desenvolvimento. Observe que, embora as Ellipsis
(...
) do Python possam servir a um propósito semelhante, elas têm um valor, tornando as Ellipsis
uma expressão.
Resumo das expressões vs declarações
Para esclarecer o assunto, dê uma olhada rápida no diagrama a seguir. Isso o ajudará a entender melhor os diferentes tipos de expressões e instruções em Python:
Em resumo, uma instrução pode ser qualquer instrução Python, seja uma única linha ou um bloco de código. Todos os quatro quadrantes do diagrama acima representam afirmações. No entanto, este termo é frequentemente usado para se referir a afirmações puras que apenas causam efeitos colaterais sem ter valor.
Por outro lado, uma expressão pura é um tipo especial de afirmação que avalia apenas algum valor sem causar efeitos colaterais. Além disso, você pode encontrar expressões com efeitos colaterais , que são declarações com um valor. São expressões que produzem um valor e também causam efeitos colaterais. Finalmente, um não-op é uma declaração que não faz-não produz um valor nem causa efeitos colaterais.
Em seguida, você aprenderá a reconhecê -los na natureza.
Como verificar se uma instrução python é uma declaração de expressão vs?
Neste ponto, você já sabe que todas as instruções do Python são tecnicamente sempre uma declaração. Portanto, uma pergunta mais específica que você pode querer se fazer é se está lidando com uma expressão ou uma instrução pura . Você responderá a esta pergunta dupla: manualmente e depois programaticamente usando o Python.
Verificando manualmente no Python REPL
Para determinar rapidamente a resposta para a pergunta colocada nesta seção, você pode aproveitar o poder do Python Repl, que avalia expressões à medida que as digitam. Se uma instrução for uma expressão, você verá imediatamente a representação padrão da string de seu valor na saída:
>>> 42 + 8
50
Esta é uma expressão aritmética avaliada como 50
. Como você não interceptou seu valor, por exemplo, atribuindo a expressão a uma variável ou passando-a para uma função como argumento, o Python exibe o resultado calculado para você. Se a mesma linha de código estivesse presente em um script Python, o intérprete teria ignorado o valor avaliado, que teria sido perdido.
Por outro lado, a execução de uma declaração pura no Repl não mostra nada na saída:
>>> import math
>>>
Esta é uma instrução importar
, que não possui um valor correspondente. Em vez disso, ele carrega o pacote Python especificado no seu espaço de nome atual como um efeito colateral.
No entanto, você precisa ter cuidado com essa abordagem. Às vezes, a representação padrão da string de um valor calculado pode ser enganoso. Considere este exemplo:
>>> fruit = {"name": "apple", "color": "red"}
>>> fruit.get("taste")
>>> fruit.get("color")
'red'
Você define um dicionário Python que representa uma fruta. Na primeira vez que você chama .get()
nele, não há saída visível. Mas então, você chama .get()
novamente com uma chave diferente, e ele retorna o valor associado a essa chave, que é 'red'
.
No primeiro caso, .get()
retorna nenhum
para indicar um par de valores de chave ausente, já que o dicionário não contém a chave "gosto"
. No entanto, quando você usa uma chave que existe, como "cor"
, o método retorna o valor correspondente.
O Python Repl nunca exibe Nenhum
A menos que seja impressa explicitamente, o que às vezes pode levar à confusão se você não tiver conhecimento desse comportamento. Em caso de dúvida, você sempre pode ligar para print()
no resultado para revelar seu verdadeiro valor:
>>> fruit.get("taste")
>>> print(fruit.get("taste"))
None
Embora isso funcione, existe uma maneira mais confiável de verificar se uma instrução é uma expressão em Python.
Você pode atribuir uma instrução a uma variável para verificar se é um valor R. Caso contrário, se for uma declaração pura, você não poderá usá -lo como um valor para sua variável e receberá esse erro:
>>> x = import math
File "<python-input-0>", line 1
x = import math
^^^^^^
SyntaxError: invalid syntax
O Python aumenta um syntaxError
para dizer que você não pode atribuir uma instrução importar
a uma variável porque essa instrução não possui valor tangível.
Um caso um tanto especial é a atribuição da cadeia, que pode inicialmente parecer que você está tentando atribuir y=42
à variável x
para verificar se é uma expressão:
>>> x = y = 42
No entanto, isso faz com que diversas variáveis —x
e y
neste caso — se refiram ao mesmo valor, 42
. É uma notação abreviada para fazer duas atribuições separadas, x=42
e y=42
.
Outra maneira de saber se um pedaço de código Python é uma expressão ou uma declaração pura envolve envolvê -lo em parênteses . Parênteses geralmente ajudam a agrupar os termos a alterar a ordem de operações padrão determinada pela precedência do operador. Mas você só pode envolver expressões, não declarações:
>>> (2 + 2)
4
>>> (x = 42)
File "<python-input-7>", line 1
(x = 42)
^^^^^^
SyntaxError: invalid syntax. Maybe you meant '==' or ':=' instead of '='?
A linha 1 contém uma expressão, que avalia para quatro, enquanto linha 4 leva a um erro de sintaxe porque o Python tenta sem sucesso avaliar uma declaração de atribuição. A mensagem de erro exibida solicita que você altere o operador de atribuição (=
) para a igualdade de valor ( ==
) ou o operador da morsa (:=
).
Até agora, você está comparando expressões e declarações manualmente no Python Repl. Na próxima seção, você aprenderá como fazer isso programaticamente, o que pode ajudar se for uma tarefa repetitiva que você deseja automatizar.
Construindo um detector de expressão python vs declaração
Para determinar se uma instrução é uma expressão ou uma instrução programaticamente, você pode chamar as funções internas eval()
e exec()
do Python. A primeira função permite avaliar expressões dinamicamente, e a segunda pode executar código arbitrário a partir de uma string:
>>> eval("2 + 2")
4
>>> exec("x = 42")
>>> print(x)
42
A string "2 + 2"
contém uma expressão Python que eval()
avalia como o número inteiro 4
. Você pode atribuir o valor retornado por eval()
a uma variável, se desejar. Por outro lado, exec()
não retorna nada, mas causa efeitos colaterais. Observe como você pode acessar a variável definida na string passada para exec()
após chamar esta função dentro do mesmo escopo.
Ambas as funções relatam um erro de sintaxe quando a sequência de entrada não é uma expressão ou instrução válida:
>>> eval("2 +")
Traceback (most recent call last):
...
SyntaxError: invalid syntax
>>> exec("x = 2 +")
Traceback (most recent call last):
...
SyntaxError: invalid syntax
Além disso, avaliar()
aumenta um erro de sintaxe quando a string fornecida contém uma instrução pura sem um valor. Simultaneamente, a mesma instrução funciona bem com EXEC()
:
>>> eval("x = 2 + 2")
Traceback (most recent call last):
...
SyntaxError: invalid syntax
>>> exec("x = 2 + 2")
Você pode aproveitar essa discrepância para diferenciar entre expressões e declarações puras. Mas lembre-se de chamar eval()
antes de exec()
para evitar falsos positivos. Como todas as expressões são instruções, exec()
quase sempre terá sucesso, quer receba uma expressão ou uma instrução como argumento:
>>> eval("print('Hello, World!')")
Hello, World!
>>> exec("print('Hello, World!')")
Hello, World!
Ambas as funções oferecem um resultado idêntico: Olá, mundo!
. Se você ligou para EXEC()
e parou por aí, poderá concluir erroneamente que o código é uma instrução enquanto pode ser uma expressão válida. Enquanto isso, todas as chamadas de função no Python são tecnicamente expressões e devem ser classificadas como tal. Veja como você pode abordar isso.
Para evitar a duplicação de código, você pode combinar avaliar()
e EXEC()
em uma única função, que delega para ast.parse() com o modo apropriado:
>>> import ast
>>> def valid(code, mode):
... try:
... ast.parse(code, mode=mode)
... return True
... except SyntaxError:
... return False
...
>>> valid("x = 2 + 2", mode="eval")
False
>>> valid("x = 2 + 2", mode="exec")
True
Esta função aceita um snippet python
e o parâmetro mode
, que pode levar um dos dois valores: "avaliar"
ou "Exec "
. A função retorna false
quando a expressão ou instrução subjacente está sintaticamente incorreta. Caso contrário, ele retorna true
quando o código é executado com sucesso no modo especificado.
Para completar, você pode escrever outra função auxiliar que fornecerá uma representação textual para o trecho de código fornecido:
>>> def describe(code):
... if valid(code, mode="eval"):
... return "expression"
... elif valid(code, mode="exec"):
... return "statement"
... else:
... return "invalid"
...
>>> describe("x = 2 + 2")
'statement'
>>> describe("2 + 2")
'expression'
>>> describe("2 +")
'invalid'
Você primeiro verifica se o código fornecido é uma expressão. Quando estiver, você retorna a string "expressão"
. Caso contrário, você pode verificar se o código é uma instrução. Se for qualificado como uma instrução, você retornará a string "statement"
. Finalmente, se nenhuma condição for atendida, você conclui que o código é "inválido"
.
Essa abordagem pode ajudar quando você precisar realizar esse teste programaticamente por qualquer motivo. Talvez você esteja construindo seu próprio REPL Python usando correspondência de padrões estruturais em Python e precise decidir quando exibir uma expressão avaliada.
Observação: se uma parte da sintaxe é uma expressão ou uma instrução não é algo definitivo. Um exemplo notável disso é a função print()
, que costumava ser a instrução print
no Python 2 legado.
Agora você deve entender de forma mais intuitiva a diferença entre expressões e instruções em Python. Você também deve ser capaz de dizer qual é qual. A próxima questão importante é se é apenas uma distinção semântica para os puristas ou se tem algum significado na sua prática diária de codificação. Você vai descobrir agora!
Essa diferença é importante na sua programação do dia-a-dia?
Na maioria das vezes, você não precisa pensar muito se está trabalhando com uma expressão ou instrução em Python. Dito isto, há duas exceções notáveis que vale a pena mencionar:
Lambda
Expressõesafirmações afirmam
Você começará com o primeiro, que é a expressão lambda
.
Expressão lambda
A palavra-chave lambda
do Python permite definir uma função anônima, que pode ser útil para operações únicas, como especificar a chave de classificação ou uma condição para filtrar:
>>> fruits = [("apple", 2), ("banana", 0), ("orange", 3)]
>>> sorted(fruits, key=lambda item: item[1])
[('banana', 0), ('apple', 2), ('orange', 3)]
>>> list(filter(lambda item: item[1] > 0, fruits))
[('apple', 2), ('orange', 3)]
Aqui, você define uma lista de tuplas de dois elementos contendo o nome de uma fruta e sua respectiva quantidade. Em seguida, você classifica esta lista em ordem crescente com base na quantidade. Por fim, você filtra a lista, deixando apenas as frutas que possuem pelo menos uma unidade.
Esta é uma abordagem conveniente, desde que você não precise fazer referência a essas funções inline além de seu uso imediato. Embora você sempre possa atribuir uma expressão lambda a uma variável para uso posterior, ela não é sintaticamente equivalente a uma função regular definida com def
.
Uma definição de função inicia um novo bloco de código que representa o corpo da função. No Python, praticamente todos os blocos de código pertencem a uma instrução composta, para que não possa ser avaliada.
Nota: Você aprenderá mais sobre instruções simples e compostas na próxima seção.
Como as declarações não têm valores, você não pode atribuir uma definição de função a uma variável como pode com uma expressão lambda:
>>> inline = lambda: 42
>>> regular = (
... def function():
... return 42
... )
...
File "<python-input-1>", line 2
def function():
^^^
SyntaxError: invalid syntax
A variável inline
mantém uma referência a uma função anônima definida como um lambda
, enquanto regular
é uma tentativa malsucedida de atribuir uma definição de função nomeada a um variável.
No entanto, você tem permissão para atribuir uma referência a uma função que já foi definida em outro lugar:
>>> def function():
... return 42
...
>>> alias = function
Observe o significado diferente da instrução def function():
e da referência function
que aparece abaixo dela. O primeiro é o modelo do que a função faz, e o segundo é o endereço da função, que você pode passar sem realmente chamar a função.
Nota: A capacidade de tratar as funções como valores é um aspecto essencial do paradigma de programação funcional. Permite composição da função e criação de funções de ordem superior.
Uma função definida com a palavra-chave lambda
deve sempre conter exatamente uma expressão em seu corpo. Ele será avaliado e retornado implicitamente sem que você precise incluir a instrução return
. Na verdade, o uso de instruções em funções lambda
é totalmente proibido. Se você tentar usá-los, causará um erro de sintaxe:
>>> lambda: pass
File "<python-input-0>", line 1
lambda: pass
^^^^
SyntaxError: invalid syntax
Você aprendeu que pass
é uma instrução, portanto não tem lugar em uma expressão lambda
. No entanto, existe uma solução alternativa para isso. Você sempre pode agrupar uma ou mais instruções em uma função e chamar essa função em sua expressão lambda, assim:
>>> import tkinter as tk
>>> def on_click(age):
... if age > 18:
... print("You're an adult.")
... else:
... print("You're a minor.")
...
>>> window = tk.Tk()
>>> button = tk.Button(window, text="Click", command=lambda: on_click(42))
>>> button.pack(padx=10, pady=10)
>>> window.mainloop()
Este é um aplicativo Python mínimo com uma interface gráfica de usuário (GUI) construída com Tkinter. A linha destacada registra um ouvinte para o evento de clique do botão. O manipulador de eventos é definido como uma expressão lambda embutida, que chama uma função wrapper que encapsula uma instrução condicional. Você não poderia expressar uma lógica tão complexa apenas com uma expressão lambda.
Afirmação de afirmação
Quanto à instrução assert
, há um ponto comum de confusão em torno dela, que decorre de sua sintaxe um tanto enganosa. É muito fácil esquecer que assert
é uma instrução e não se comporta como uma chamada de função comum. Às vezes, isso pode causar comportamento indesejado quando você não toma cuidado.
Em sua forma mais básica, a palavra -chave afirma
deve ser seguida por um predicado lógico ou uma expressão que avalia um valor booleano:
>>> assert 18 < int(input("What's your age? "))
What's your age? 42
>>> assert 18 < int(input("What's your age? "))
What's your age? 15
Traceback (most recent call last):
...
AssertionError
Se a expressão se tornar verdadeira, então nada acontece. Se a expressão for falsamente, o Python levantar um AssertionError
, desde que você não tenha desativado as afirmações com uma opção de linha de comando ou uma variável de ambiente.
Opcionalmente, você pode anexar uma mensagem de erro personalizada para ser exibida junto com o erro de asserção gerado. Para fazer isso, coloque uma vírgula após o predicado e inclua uma string literal – ou qualquer expressão que seja avaliada como um:
>>> assert 18 < int(input("What's your age? ")), "You're a minor"
What's your age? 15
Traceback (most recent call last):
...
AssertionError: You're a minor
Até agora tudo bem. Porém, incorporar uma mensagem de erro mais longa pode levar a um código menos legível, tentando você a quebrar essa linha de alguma forma.
O Python oferece um recurso interessante chamado continuação de linha implícita, que permite dividir uma longa instrução em várias linhas sem usar uma barra de barragem explícita (\
). Você pode conseguir isso envolvendo suas expressões em parênteses , colchetes ou aparelhos curiosos , tornando o código mais organizado e mais fácil de ler:
>>> assert (
... 18 < int(input("What's your age? ")),
... "You're a minor"
... )
<python-input-0>:1: SyntaxWarning: assertion is always true, perhaps remove parentheses?
assert (
What's your age? 15
>>>
Isso geralmente funciona, mas não neste caso. Como você pode ver, as versões modernas do Python até emitirão um aviso para avisar que provavelmente algo está errado.
Lembre-se de que uma instrução assert
espera uma expressão logo após a palavra-chave. Ao colocar seu predicado e mensagem personalizada entre parênteses, você essencialmente define uma tupla, que é tratada como uma única expressão. Python avalia essas sequências não vazias como True
. Portanto, sua instrução assert
sempre será aprovada, independentemente da condição real que você está tentando verificar.
Para evitar esse problema, é melhor usar uma continuação explícita de linha quando deseja quebrar uma linha longa:
>>> assert 18 < int(input("What's your age? ")), \
... "You're a minor"
What's your age? 15
Traceback (most recent call last):
...
AssertionError: You're a minor
Agora, você observa o comportamento esperado novamente porque o predicado é avaliado corretamente.
Anteriormente, você aprendeu que os blocos de código no Python fazem parte de instruções compostas. Em seguida, você explorará as diferenças entre declarações simples e compostas no Python.
O que são instruções simples e compostas em Python?
As declarações são os principais blocos de construção de seus programas Python. Eles permitem controlar o fluxo da execução e executar ações, como atribuir valores a variáveis. Você pode categorizar declarações em dois tipos primários:
- Declarações simples
- Declarações compostas
Instruções simples podem caber em uma única linha, enquanto instruções compostas compreendem outras instruções que normalmente abrangem várias linhas de código recuado seguidas por um caractere de nova linha.
A tabela abaixo mostra alguns exemplos comuns de ambos os tipos de declarações:
assert age > 18
Se idade> 18: ...
import math
enquanto true: ...
return 42
Para _ em intervalo (3): ...
pass
tente: ...
x = 42 + 8
def add(x, y): ...
Como você pode ver, declarações simples causam um efeito colateral específico - exceto para a instrução aprovar
, o que não. As declarações compostas, por outro lado, incluem construções tradicionais de fluxo de controle, como loops, condicionais e definições de função.
Ao examinar mais de perto as instruções compostas, você descobrirá que elas consistem em uma ou mais cláusulas, cada uma contendo um cabeçalho e um conjunto. Considere a seguinte declaração condicional como exemplo:
if age > 18:
print("Welcome!")
elif age < 18:
raise ValueError("You're too young to be here.")
else:
import sys
sys.exit(1)
As linhas destacadas representam os cabeçalhos das cláusulas da instrução composta, enquanto as linhas restantes representam seus conjuntos correspondentes. Por exemplo, a cláusula if verifica se a idade é maior que dezoito anos e chama condicionalmente a função print()
dentro de seu conjunto.
Todos os cabeçalhos da cláusula dentro de uma declaração composta estão alinhados no mesmo nível de indentação. Eles começam com uma palavra -chave Python, como se
, elif
ou else
, e concluem com um cólon (:
) Isso marca o início de um bloco de código da suíte. Uma suíte é uma coleção de declarações governadas por sua respectiva cláusula. Nesse caso, as três cláusulas determinam quais das suítes executarem com base na condição etária.
Existe um tipo especial de uma declaração composta conhecida como Lista de Declaração , que consiste em uma sequência de declarações simples. Embora cada um deles deva ser colocado em uma única linha, você pode espremer várias instruções simples em uma única linha. Você sabe como?
Como colocar várias declarações em uma única linha?
Na maioria dos casos, é preferível colocar apenas uma declaração ou expressão por linha por uma questão de legibilidade. No entanto, se você insistir em encaixar mais de um na mesma linha, poderá usar o semicolon (;
) como um separador de instrução.
Um caso de uso popular em que isso pode ser útil envolve a execução de um programa de uma linha na linha de comando usando a opção python -c
:
$ python -c 'import sys; print(sys.version)'
3.13.0 (main, Oct 19 2024, 15:05:58) [GCC 13.2.0]
Isso permite testar ideias rapidamente na forma de pequenos trechos de código. Mas hoje em dia, a maioria dos terminais permite espalhar o código em várias linhas sem problemas:
$ python -c '
> import sys
> print(sys.version)
> '
3.13.0 (main, Oct 19 2024, 15:05:58) [GCC 13.2.0]
O comando que o intérprete Python espera pode consistir em várias linhas.
Outro caso de uso comum para o semicolon é inserir um ponto de interrupção no seu código. Antes do Python 3.7, que introduzia a função de Breakpoint()
integrada, você entraria no depurador com a seguinte linha idiomática de código:
import pdb; pdb.set_trace()
Quando o intérprete hits pdb.set_trace()
, ele será interrompido e você inserirá a sessão de depuração interativa usando o Python Debugger (PDB).
Além disso, você pode tentar usar esse truque para ofuscar ou reduzir intencionalmente seu código Python até certo ponto. No entanto, você não será capaz de escapar de algumas limitações sintáticas, então obterá melhores resultados com ferramentas externas como o Pyarmor.
Antes de fechar este tutorial, você precisa responder a uma pergunta final sobre expressões e declarações no Python. Um que lhe dará a base para enfrentar desafios de programação mais avançados.
As declarações podem ter uma natureza dupla em Python?
Como todas as instruções no Python são declarações, você pode executar uma expressão que tem efeitos colaterais sem considerar o valor calculado. Geralmente, esse é o caso quando você chama uma função ou método, mas ignora seu valor de retorno:
>>> with open("file.txt", mode="w", encoding="utf-8") as file:
... file.write("Hello, World!")
...
13
No exemplo acima, você chama o método .Write()
OK. Portanto, você pode aproveitar as expressões apenas pelos efeitos colaterais, se houver. Mas e o contrário? Você pode avaliar declarações? Você está prestes a descobrir!
Tarefas
Algumas linguagens de programação confundem os limites entre instruções e expressões. Por exemplo, você pode avaliar uma instrução de atribuição em C ou C++:
#include <stdio.h>
int main() {
int x;
while (x = fgetc(stdin)) {
if (x == EOF)
break;
putchar(x);
}
return 0;
}
Este pequeno programa ecoa tudo o que o usuário digita no teclado. Observe a linha destacada, que obtém o próximo caractere da entrada padrão e o atribui a uma variável local, x
. Simultaneamente, esta atribuição é avaliada como uma expressão booleana e usada como condição de continuação para o loop while
. Em outras palavras, enquanto o número ordinal do caractere de entrada for diferente de zero, o loop continua.
Esta tem sido uma fonte notória de bugs que atormentam os programas C e C++ há muito tempo. Os programadores muitas vezes usavam erroneamente o operador de atribuição (=
) em tais condições, em vez do operador de teste de igualdade pretendido (==
) devido à sua semelhança visual. Embora o exemplo acima use esse recurso intencionalmente, normalmente foi o resultado de um erro humano que pode levar a um comportamento inesperado.
Por muito tempo, os principais desenvolvedores evitaram implementar um recurso semelhante em Python devido a preocupações com essa possível confusão. Esse foi o caso até o Python 3.8, que introduziu o operador walrus (:=
) para permitir expressões de atribuição:
MAX_BYTES = 1024
buffer = bytearray()
with open("audio.wav", mode="rb") as file:
while chunk := file.read(MAX_BYTES):
buffer.extend(chunk)
Neste trecho de código, você lê incrementalmente um arquivo WAV em partes binárias até encontrar o byte de controle de fim de arquivo, que é indicado por um pedaço vazio. No entanto, em vez de verificar explicitamente se o pedaço retornado por .read()
não está vazio - ou se é avaliado como True
- você aproveita a expressão de atribuição para executar e avaliar a tarefa em uma única etapa.
Isso economiza algumas linhas de código, que de outra forma seriam assim:
MAX_BYTES = 1024
buffer = bytearray()
with open("audio.wav", mode="rb") as file:
while True:
chunk = file.read(MAX_BYTES)
if not chunk:
break
buffer.extend(chunk)
Você transformou um loop determinístico em um loop infinito e adicionou mais três instruções: a instrução de atribuição, a instrução condicional e a instrução break
.
Ao contrário do exemplo C, não há como confundir a expressão de atribuição com sua contraparte de instrução. O Python continua a proibir declarações de atribuição em um contexto booleano. Nessas ocasiões, você deve usar explicitamente o operador Walrus, que se alinha bem com um dos aforismos do zen de Python:
Explícito é melhor que implícito. (Fonte)
Existem outros exemplos de instruções que possuem suas expressões análogas em Python. A seguir, você examinará declarações e expressões condicionais.
Condicionais
Muitas linguagens de programação fornecem um operador condicional ternário , que combina três elementos: uma condição lógica, um valor se a condição avaliar para true
e um valor alternativo, caso a condição seja avaliada para false
.
Nas linguagens da família C, o operador ternário (?:
) lembra o emoticon de Elvis Presley com seu penteado distinto. Embora essas linguagens o chamem de operador Elvis, Python se atém ao termo mais conservador, expressão condicional. Aqui está o que parece:
>>> def describe(users):
... if not users:
... print("No people")
... else:
... print(len(users), "people" if len(users) > 1 else "person")
...
>>> describe([])
No people
>>> describe(["Alice"])
1 person
>>> describe(["Alice", "Bob"])
2 people
À primeira vista, a expressão condicional de Python se assemelha à declaração condicional padrão condensada em uma única linha. Começa com uma expressão associada a um valor verdadeiro, seguido pela condição a ser verificada e, em seguida, a expressão correspondente a um valor falsamente. Ele permite avaliar a lógica condicional, o que normalmente exigiria o uso de uma instrução condicional.
Compreensões
Outro exemplo de declarações que ocupam a área cinzenta são todos os tipos de expressões de compreensão , como a compreensão da lista ou a expressão do gerador, das quais você pode aproveitar para evitar loops explícitos:
>>> fruits = [("apple", 2), ("banana", 0), ("orange", 3)]
>>> sorted(name.title() for name, quantity in fruits if quantity > 0)
['Apple', 'Orange']
Novamente, a sintaxe é semelhante ao loop for
e à instrução if
, mas você os recolhe em uma linha e reorganiza suas partes individuais. Isso só funciona bem quando a condição e a expressão a serem avaliadas são razoavelmente pequenas para que você possa encaixá-las em uma ou duas linhas. Caso contrário, você acabará com um código desordenado e difícil de ler.
Por fim, os geradores do Python merecem menção aqui porque usam sintaxe que podem ser uma expressão e uma declaração ao mesmo tempo.
Geradores
As palavras-chave yield
e yield from
aparecem dentro de funções geradoras, que permitem lidar com grandes fluxos de dados de forma eficiente ou definir corrotinas.
Você deixa o python executar render
como uma instrução quando deseja produzir valores a partir de uma função do gerador:
>>> import random
>>> def generate_noise(size):
... for _ in range(size):
... yield 2 * random.random() - 1
...
>>> list(generate_noise(3))
[0.7580438973021972, 0.5273057193944659, -0.3041263216813208]
Este gerador é um produtor de valores aleatórios entre -1 e 1. Usando rendimento
Aqui é um pouco semelhante ao uso da instrução retornar
. Ele retransmite uma série de valores para o chamador como efeito colateral, mas, em vez de encerrar completamente a função, a instrução rendimento
suspende a execução da função, permitindo retomar e continuar posteriormente.
Agora, você pode opcionalmente avaliar rendimento
como uma expressão para transformar seu gerador em um prosumidor (produtor e consumidor) com potencialmente muitos pontos de entrada e saída. Cada expressão yield
pode fornecer e receber valores nos dois sentidos. Os valores produzidos representam a saída do gerador, enquanto os valores enviados de volta ao gerador são a entrada:
>>> def lowpass_filter():
... a = yield
... b = yield a
... while True:
... a, b = b, (yield (a + b) / 2)
...
A primeira expressão rendimento
não gera implicitamente nada ( nenhum
), mas recebe um valor do mundo exterior quando avaliado como uma expressão. Este valor é então atribuído a uma variável local chamada a
.
A segunda expressão yield
produz a
, enquanto armazena simultaneamente outro valor em b
. Neste ponto, o filtro fica saturado e pronto para processar os dados recebidos. A capacidade de consumir e produzir valores em cada ponto de suspensão (rendimento
) torna possível enviar um sinal através do seu filtro como uma corrente elétrica flui através de um circuito.
A última expressão yield
calcula e produz a média de ambos os números, substitui a
por b
e, em seguida, atribui um novo valor a b
.
Nota: Os parênteses em torno da expressão rendem
dentro do loop são necessários para evitar a ambiguidade na ordem de avaliação. Eles comunicam explicitamente que sua intenção é tratar render
como uma expressão, que deve ser avaliada antes da atribuição.
Você pode conectar os dois geradores um ao outro chamando .send()
em seu filtro passa-baixa, que calcula uma média móvel de dois pontos que suaviza o sinal:
>>> lf = lowpass_filter()
>>> lf.send(None)
>>> for value in generate_noise(10):
... print(f"{value:>5.2f}: {lf.send(value):>5.2f}")
...
0.66: 0.66
-0.01: 0.32
0.30: 0.15
-0.10: 0.10
-0.98: -0.54
0.79: -0.10
0.31: 0.55
-0.10: 0.11
-0.25: -0.18
0.57: 0.16
Os números na coluna da esquerda vêm diretamente do gerador de ruído e são alimentados diretamente no filtro passa-baixa. A coluna da direita contém a saída desse filtro. Observe que você deve primeiro preparar seu prosumer enviando None
ou chamando next()
para que ele avance para a primeira expressão yield
.
Conclusão
Neste tutorial, você explorou as diferenças fundamentais entre expressões e instruções em Python. Você aprendeu que expressões são partes de sintaxe avaliadas como um valor, enquanto instruções são instruções que podem causar efeitos colaterais. Ao mesmo tempo, existe uma área cinzenta entre eles.
Compreender essa distinção é crucial para os desenvolvedores do Python, pois afeta como você escreve e estrutura seu código. Agora que você tem essas habilidades, pode escrever um código Python mais preciso e expressivo, fazendo uso total de expressões e declarações para criar programas robustos e eficientes.