"Argumentar que você não se importa com o direito à privacidade porque não tem nada a esconder não é diferente de dizer que não se importa com a liberdade de expressão porque não tem nada a dizer." (Edward Snowden, 2015)
A cifra de César não é segura; não é preciso muito para um computador forçar brutamente todas as 66 chaves possíveis. A cifra de transposição, por outro lado, é mais difícil de forçar brutamente porque o número de chaves possíveis depende do comprimento da mensagem. Existem muitos tipos diferentes de cifras de transposição, incluindo a cifra de cerca de trilho, cifra de rota, cifra de transposição de Myszkowski e cifra de transposição interrompida. Este capítulo aborda uma cifra de transposição simples chamada cifra de transposição colunar.
Criando funções com instruções def;
Argumentos e parâmetros;
Variáveis em escopos globais e locais;
Funções main();
O tipo de dados de lista;
Semelhanças em listas e strings;
Listas de listas;
Operadores de atribuição aumentados (+=, -=, *=, /=);
O método de string join();
Valores de retorno e a instrução return;
A variável __name__.
Em vez de substituir caracteres por outros caracteres, a cifra de transposição reorganiza os símbolos da mensagem em uma ordem que torna a mensagem original ilegível. Como cada chave cria uma ordenação diferente, ou permutação, dos caracteres, um criptoanalista não sabe como reorganizar o texto cifrado de volta à mensagem original.
As etapas para criptografar com a cifra de transposição são as seguintes:
Conte o número de caracteres na mensagem e a chave;
Desenhe uma linha com um número de caixas igual à chave (por exemplo, 8 caixas para uma chave de 8);
Comece a preencher as caixas da esquerda para a direita, inserindo um caractere por caixa;
Quando você ficar sem caixas, mas ainda tiver mais caracteres, adicione outra fileira de caixas;
Quando chegar ao último caractere, sombreie as caixas não utilizadas na última linha;
Começando do canto superior esquerdo e descendo cada coluna, escreva os caracteres. Quando chegar ao final de uma coluna, vá para a próxima coluna à direita. Pule todas as caixas sombreadas. Este será o texto cifrado.
Para ver como essas etapas funcionam na prática, criptografaremos uma mensagem manualmente e então traduziremos o processo em um programa.
Antes de começarmos a escrever o código, vamos criptografar a mensagem “O senso comum não é tão comum.” usando lápis e papel. Incluindo os espaços e a pontuação, esta mensagem tem 30 caracteres. Para este exemplo, você usará o número 8 como chave. O intervalo para chaves possíveis para este tipo de cifra é de 2 a metade do tamanho da mensagem, que é 15. Mas quanto maior a mensagem, mais chaves são possíveis. Criptografar um livro inteiro usando a cifra de transposição colunar permitiria milhares de chaves possíveis.
O primeiro passo é desenhar oito caixas em uma fileira para corresponder ao número da chave, conforme mostrado na Figura 1:
Figura 1: O número de caixas na primeira linha deve corresponder ao número da chave.
O segundo passo é começar a escrever a mensagem que você quer criptografar nas caixas, colocando um caractere em cada caixa, como mostrado na Figura 2. Lembre-se de que espaços também são caracteres (indicados aqui com ■).
Figura 2: Preencha um caractere por caixa, incluindo espaços.
Você tem apenas oito caixas, mas há 30 caracteres na mensagem. Quando acabarem as caixas, desenhe outra fileira de oito caixas sob a primeira fileira. Continue criando novas fileiras até que tenha escrito a mensagem inteira, como mostrado na Figura 3:
Figura 3: Adicione mais linhas até que toda a mensagem esteja preenchida.
Sombreie as quatro caixas na última linha como um lembrete para ignorá-las. O texto cifrado consiste nas letras lidas da caixa superior esquerda descendo a coluna. O, c, o, c são da 1ª coluna, conforme rotulado no diagrama. Quando chegar à última linha de uma coluna, mova para a linha superior da próxima coluna à direita. Os próximos caracteres são ■, o, ■, o. Ignore as caixas com os asteriscos.
O texto cifrado é Ococ o osmémeu unmtms ã.ono ã , que está suficientemente embaralhado para impedir que alguém descubra a mensagem original apenas olhando para ela.
Criando o programa de criptografia
Para fazer um programa para criptografar, você precisa traduzir essas etapas de papel e lápis para o código Python. Vamos dar uma olhada novamente em como criptografar a string “O senso comum não é tão comum.” usando a chave 8. Para o Python, a posição de um caractere dentro de uma string é seu índice numerado, então adicione o índice de cada letra na string às caixas em seu diagrama de criptografia original, conforme mostrado na Figura 4. Lembre-se de que os índices começam com 0, não 1:
Figura 4: Adicione o número do índice a cada caixa, começando com 0.
Essas caixas mostram que a primeira coluna tem os caracteres nos índices 0, 8, 16 e 24 (que são O, c, o e o). A próxima coluna tem os caracteres nos índices 1, 9, 17 e 25 (que são ■, o, ■ e o). Observe o padrão emergindo: a enésima coluna tem todos os caracteres na string nos índices 0 + (n − 1), 8 + (n − 1), 16 + (n − 1) e 24 + (n − 1), conforme mostrado na Figura 5:
Figura 5: O índice de cada caixa segue um padrão previsível.
Há uma exceção para a última linha nas colunas 7 e 8, porque 24 + (7 – 1) e 24 + (8 – 1) seriam maiores que 29, que é o maior índice na string. Nesses casos, você só adiciona 0, 8 e 16 a n (e pula 24).
O que há de tão especial nos números 0, 8, 16 e 24? Esses são os números que você obtém quando, começando do 0, você adiciona a chave (que neste exemplo é 8). Então, 0 + 8 é 8, 8 + 8 é 16, 16 + 8 é 24. O resultado de 24 + 8 seria 32, mas como 32 é maior que o comprimento da mensagem, você parará em 24.
Para a string da coluna n, comece no índice (n – 1) e continue adicionando 8 (a chave) para obter o próximo índice. Continue adicionando 8 enquanto o índice for menor que 30 (o comprimento da mensagem), nesse ponto mova para a próxima coluna.
Se você imaginar que cada coluna é uma string, o resultado seria uma lista de oito strings, como esta: Ococ, o o, smém, eu u, nmtm, s ã., ono, ã . Se você concatenasse as strings em ordem, o resultado seria o texto cifrado: Ococ o osmémeu unmtms ã.ono ã . Você aprenderá sobre um conceito chamado listas mais adiante no capítulo que permitirá que você faça exatamente isso.
Código fonte para o programa de criptografia de cifra de transposição
def main():
"""Função responsável pelo programa da cifra de transposição."""
# --- Determinar a frase e a chave --- #
minha_mensagem = 'O senso comum não é tão comum.'
minha_chave = 8
# --- Obter a mensagem cifrada --- #
mensagem_cifrada = encriptar_mensagem(minha_chave, minha_mensagem)
# --- Colocar um pipe (|) após o fim da mensagem criptografada para indicar o fim --- #
print(mensagem_cifrada + '|')
def encriptar_mensagem(chave, mensagem):
"""
Função responável por encriptar a mensagem.
:param chave: Chave para a criptografia.
:param mensagem: Mensagem a ser encriptada.
"""
# --- Cada string no texto cifrado é representado como uma coluna na tabela --- #
texto_cifrado = [''] * chave
# --- Loop por cada coluna do texto cifrado --- #
for coluna in range(chave):
index_atual = coluna
# --- Manter o loop até que index_atual ultrapasse o comprimento da mensagem --- #
while index_atual < len(mensagem):
# --- Colocar o caractere presente na posição do valor index_atual em mensagem no final da coluna atual --- #
texto_cifrado[coluna] += mensagem[index_atual]
# --- Mover o valor de index_atual --- #
index_atual += chave
# --- Juntar os itens da lista como uma string só --- #
return ''.join(texto_cifrado)
# --- Chamar a função --- #
if __name__ == '__main__':
main()
Exemplo de execução do programa de criptografia de cifra de transposição
Quando você executa o programa, ele produz esta saída:
Ococ o osmémeu unmtms ã.ono ã |
O caractere de pipe vertical (|) marca o fim do texto cifrado caso haja espaços no final. Este texto cifrado (sem o caractere de pipe no final). Se você quiser criptografar uma mensagem diferente ou usar uma chave diferente, altere o valor atribuído às variáveis minha_mensagem e minha_chave. Em seguida, execute o programa novamente.
Criando suas próprias funções com instruções def
Você usará uma instrução def para criar uma função personalizada, main():
def main():
"""Função responsável pelo programa da cifra de transposição."""
# --- Determinar a frase e a chave --- #
minha_mensagem = 'O senso comum não é tão comum.'
minha_chave = 8
A declaração def significa que você está criando, ou definindo, uma nova função que você pode chamar mais tarde no programa. O bloco de código após a declaração def é o código que será executado quando a função for chamada. Quando você chama essa função, a execução se move dentro do bloco de código após a declaração def da função.
Como você aprendeu no Capítulo 3, em alguns casos, as funções aceitarão argumentos, que são valores que a função pode usar com seu código. Por exemplo, print() pode receber um valor de string como argumento entre seus parênteses. Quando você define uma função que recebe argumentos, você coloca um nome de variável entre seus parênteses em sua declaração def. Essas variáveis são chamadas de parâmetros. A função main() definida aqui não tem parâmetros, então ela não recebe argumentos quando é chamada. Se você tentar chamar uma função com muitos ou poucos argumentos para o número de parâmetros que a função tem, o Python gerará uma mensagem de erro.
Definindo uma função que aceita argumentos com parâmetros
Vamos criar uma função com um parâmetro e então chamá-la com um argumento. Abra uma nova janela do editor de arquivos e insira o seguinte código nela:
def ola(nome): # 1
print('Olá, ' + nome) # 2
print('Início') # 3
ola('Maria') # 4
print('Chamar a função novamente:') # 5
ola('João') # 6
print('Fim') # 7
Quando o programa é executado, a execução começa no topo. A declaração def (1) define a função ola() com um parâmetro, que é variável nome. A execução pula o bloco após a declaração def (2) porque o bloco só é executado quando a função é chamada. Em seguida, ele executa print('Início') (3), e é por isso que 'Início' é a primeira string impressa quando você executa o programa.
A próxima linha após print('Início') é a primeira chamada de função para ola(). A execução do programa salta para a primeira linha no bloco (2) da função ola(). A string 'Maria' é passada como argumento e é atribuída ao nome do parâmetro. Esta chamada de função imprime a string 'Olá, Maria' na tela.
Quando a execução do programa atinge o final do bloco da instrução def, a execução salta de volta para a linha com a chamada de função (4) e continua executando o código a partir daí, então 'Chamar a função novamente:' é impresso (5).
A seguir, há uma segunda chamada para ola() (6). A execução do programa salta de volta para a definição (2) da função ola() e executa o código lá novamente, exibindo 'Olá, João' na tela. Então, a função retorna e a execução vai para a próxima linha, que é a instrução print('Fim') (7), e a executa. Esta é a última linha do programa, então o programa sai.
Alterações nos parâmetros existem apenas dentro da função
Insira o seguinte código no shell interativo. Este código define e então chama uma função chamada func(). Note que o shell interativo requer que você insira uma linha em branco após param = 42 para fechar o bloco da instrução def:
def func(param):
param = 42
s = 'Olá'
func(s)
print(s)
A função func() pega um parâmetro chamado param e define seu valor como 42. O código fora da função cria uma variável de spam e a define como um valor de string, e então a função é chamada em s e o s é impresso.
Quando você executa este programa, a chamada print() na última linha imprimirá Olá, não 42. Quando func() é chamada com s como argumento, apenas o valor dentro de s está sendo copiado e atribuído a param. Quaisquer alterações feitas em param dentro da função não alterarão o valor na variável s.
Toda vez que uma função é chamada, um escopo local é criado. Variáveis criadas durante uma chamada de função existem neste escopo local e são chamadas de variáveis locais. Parâmetros sempre existem em um escopo local (eles são criados e recebem um valor quando a função é chamada). Pense em um escopo como um contêiner dentro do qual as variáveis existem. Quando a função retorna, o escopo local é destruído e as variáveis locais que estavam contidas no escopo são esquecidas.
Variáveis criadas fora de cada função existem no escopo global e são chamadas de variáveis globais. Quando o programa sai, o escopo global é destruído, e todas as variáveis no programa são esquecidas. (Todas as variáveis nos programas cifra reversa e cifra de César presentes no Capítulo 4 e Capítulo 5, respectivamente, eram globais).
Uma variável deve ser local ou global; não pode ser ambas. Duas variáveis diferentes podem ter o mesmo nome, desde que estejam em escopos diferentes. Elas ainda são consideradas duas variáveis diferentes, semelhante a como a Av. Brasil em Colombo (PR) é uma rua diferente da Av. Brasil em Rio de Janeiro (RJ).
A ideia importante a entender é que o valor do argumento que é “passado” para uma chamada de função é copiado para o parâmetro. Então, mesmo que o parâmetro seja alterado, a variável que forneceu o valor do argumento não é alterada.
Definindo a função main()
Você pode ver que definimos uma função main() que definirá valores para as variáveis minha_mensagem e minha_chave quando chamadas:
def main():
"""Função responsável pelo programa da cifra de transposição."""
# --- Determinar a frase e a chave --- #
minha_mensagem = 'O senso comum não é tão comum.'
minha_chave = 8
O restante dos programas neste livro também terá uma função chamada main() que é chamada no início de cada programa. O motivo pelo qual temos uma função main() é explicado no final deste capítulo, mas por enquanto saiba apenas que main() é sempre chamada logo após os programas neste livro serem executados.
As duas primeiras linhas no bloco de código que define main(). Nessas linhas, as variáveis minha_mensagem e minha_chave armazenam a mensagem de texto simples a ser criptografada e a chave usada para fazer a criptografia. A próxima linha é uma linha em branco, mas ainda faz parte do bloco e separa as linhas para tornar o código mais legível. A linha seguinte da função atribui a variável mensagem_cifrada como a mensagem criptografada chamando uma função que recebe dois argumentos:
# --- Obter a mensagem cifrada --- #
mensagem_cifrada = encriptar_mensagem(minha_chave, minha_mensagem)
O código que faz a criptografia real está na função encriptar_mensagem() definida mais adiante. Essa função recebe dois argumentos: um valor inteiro para a chave e um valor de string para a mensagem a ser criptografada. Nesse caso, passamos as variáveis minha_mensagem e minha_chave, que acabamos de definir no começo da função. Ao passar vários argumentos para uma chamada de função, separe os argumentos com uma vírgula.
O valor de retorno de encriptar_mensagem() é um valor de string do texto cifrado criptografado. Essa string é armazenada em mensagem_cifrada.
A mensagem de texto cifrado é impressa na tela:
# --- Colocar um pipe (|) após o fim da mensagem criptografada para indicar o fim --- #
print(mensagem_cifrada + '|')
O programa imprime um caractere de barra vertical (|) no final da mensagem para que o usuário possa ver quaisquer caracteres de espaço vazios no final do texto cifrado.
Esta é a última linha da função main(). Após sua execução, o programa retorna para a linha após a linha que o chamou.
Passando a chave e a mensagem como argumentos
As variáveis de chave e mensagem entre parênteses são parâmetros:
def encriptar_mensagem(chave, mensagem):
Quando a função encriptar_mensagem() é chamada, dois valores de argumento são passados (os valores em minha_chave e minha_mensagem). Esses valores são atribuídos aos parâmetros chave e mensagem quando a execução se move para o topo da função.
Você pode se perguntar por que você tem os parâmetros chave e mensagem, já que você já tem as variáveis minha_chave e minha_mensagem na função main(). Precisamos de variáveis diferentes porque minha_chave e minha_mensagem estão no escopo local da função main() e não podem ser usadas fora de main().
O tipo de dados da lista
O programa usa um tipo de dado chamado lista:
# --- Cada string no texto cifrado é representado como uma coluna na tabela --- #
texto_cifrado = [''] * chave
Antes de prosseguirmos, você precisa entender como as listas funcionam e o que você pode fazer com elas. Um valor de lista pode conter outros valores. Semelhante a como as strings começam e terminam com aspas, um valor de lista começa com um colchete aberto, [, e termina com um colchete fechado, ]. Os valores armazenados dentro da lista estão entre os colchetes. Se mais de um valor estiver na lista, os valores serão separados por vírgulas.
Para ver uma lista em ação, digite o seguinte no shell interativo:
animais = ['tamanduá', 'pangolim', 'antilope', 'cavalo']
animais
A variável animals armazena um valor de lista, e nesse valor de lista há quatro valores de string. Os valores individuais dentro de uma lista também são chamados de itens ou elementos. Listas são ideais para usar quando você tem que armazenar vários valores em uma variável.
Muitas das operações que você pode fazer com strings também funcionam com listas. Por exemplo, indexação e fatiamento funcionam em valores de lista da mesma forma que funcionam em valores de string. Em vez de caracteres individuais em uma string, o índice se refere a um item em uma lista. Insira o seguinte no shell interativo:
animais = ['tamanduá', 'pangolim', 'antilope']
animais[0] # 1
animais[1]
animais[2]
animais[1:3] # 2
Tenha em mente que o primeiro índice é 0, não 1 (1). Semelhante a como usar fatias com uma string fornece uma nova string que é parte da string original, usar fatias com uma lista fornece uma lista que é parte da lista original. E lembre-se de que se uma fatia tiver um segundo índice, a fatia só vai até, mas não inclui, o item no segundo índice (2).
Um loop for também pode iterar sobre os valores em uma lista, assim como pode iterar sobre os caracteres em uma string. O valor armazenado na variável do loop for é um único valor da lista. Insira o seguinte no shell interativo:
for comida in ['macarronada', 'pizza', 'sopa']:
print(f'Para o jantar nós temos {comida}')
Cada vez que o loop itera, a variável comida recebe um novo valor da lista, começando com o índice 0 da lista até o final da lista.
Reatribuindo os itens nas listas
Você também pode modificar os itens dentro de uma lista usando o índice da lista com uma instrução de atribuição normal. Insira o seguinte no shell interativo:
animais = ['tamanduá', 'pangolim', 'antilope']
animais[2] = 9999 # 1
animais
Para modificar o terceiro membro da lista de animais, usamos o índice para obter o terceiro valor com animais[2] e então usamos uma declaração de atribuição para alterar seu valor de 'antilope' para o valor 9999 (1). Quando verificamos o conteúdo da lista novamente, 'antilope' não está mais contido na lista.
Listas de listas
Os valores da lista podem até conter outras listas. Insira o seguinte no shell interativo:
lista = [['cachorro', 'gato'], [1, 2, 3]]
lista[0]
lista[0][0]
lista[0][1]
lista[1][0]
lista[1][1]
O valor de lista[0] avalia para a lista ['cachorro', 'gato'], que tem seus próprios índices. Os colchetes de índice duplo usados para lista[0][0] indicam que estamos pegando o primeiro item da primeira lista: lista[0] avalia para ['cachorro', 'gato'] e ['cachorro', 'gato'][0] avalia para 'cachorro'.
Usando len() e o operador in com listas
Você usou len() para indicar o número de caracteres em uma string (ou seja, o comprimento da string). A função len() também funciona em valores de lista e retorna um inteiro do número de itens em uma lista.
Digite o seguinte no shell interativo:
animais = ['tamanduá', 'pangolim', 'antilope', 'cavalo']
len(animais)
De modo similar, você usou os operadores in e not in para indicar se uma string existe dentro de outro valor de string. O operador in também funciona para verificar se um valor existe em uma lista, e o operador not in verifica se um valor não existe em uma lista. Insira o seguinte no shell interativo:
animais = ['tamanduá', 'pangolim', 'antilope', 'cavalo']
'pangolim' in animais
'cavalo' not in animais
'anti' in animais # 1
'anti' in animais[2] # 2
'macarronada' in animais
Por que a expressão em 1 retorna False enquanto a expressão em 2 retorna True? Lembre-se de que animais é um valor de lista, enquanto animais[1] avalia para o valor de string 'antilope'. A expressão em 1 avalia para False porque a string 'anti' não existe na lista animais. No entanto, a expressão em 2 avalia para True porque animais[1] é a string 'antilope' e 'anti' existe dentro dessa string.
Semelhante a como um conjunto de aspas vazias representa um valor de string em branco, um conjunto de colchetes vazios representa uma lista em branco. Insira o seguinte no shell interativo:
animais = []
len(animais)
A lista de animais está vazia, então seu comprimento é 0.
Concatenação e replicação de listas com os operadores + e *
Você sabe que os operadores + e * podem concatenar e replicar strings; os mesmos operadores também podem concatenar e replicar listas. Insira o seguinte no shell interativo:
['Olá'] + ['mundo!']
['Olá'] * 5
Já chega de similaridades entre strings e listas. Apenas lembre-se de que a maioria das operações que você pode fazer com valores de string também funcionam com valores de lista.
O algoritmo de criptografia de transposição
Usaremos listas em nosso algoritmo de criptografia para criar nosso texto cifrado. Vamos retornar ao código. Como vimos anteriormente, a variável texto_cifrado é uma lista de valores de string vazios:
# --- Cada string no texto cifrado é representado como uma coluna na tabela --- #
texto_cifrado = [''] * chave
Cada string na variável texto_cifrado representa uma coluna da grade da cifra de transposição. Como o número de colunas é igual à chave, você pode usar a replicação de lista para multiplicar uma lista com um valor de string em branco pelo valor na chave. É assim que esse pedaço do código avalia uma lista com o número correto de strings em branco. Os valores da string receberão todos os caracteres que vão para uma coluna da grade. O resultado será uma lista de valores de string que representam cada coluna, conforme discutido anteriormente no capítulo. Como os índices de lista começam com 0, você também precisará rotular cada coluna começando em 0. Portanto, texto_cifrado[0] é a coluna mais à esquerda, texto_cifrado[0] é a coluna à direita dela e assim por diante.
Para ver como isso funcionaria, vamos olhar novamente para a grade do exemplo “O senso comum não é tão comum.” do início deste capítulo (com números de coluna correspondentes aos índices da lista adicionados ao topo), conforme mostrado na Figura 6:
Figura 6: A grade de mensagens de exemplo com índices de lista para cada coluna
Se atribuíssemos manualmente os valores da string à variável texto_cifrado para esta grade, ficaria assim:
texto_cifrado = ['Ococ', ' o o', 'smém', 'eu m', 'nmtm', 's ã.', 'ono', ' ã ']
texto_cifrado
O próximo passo adiciona texto a cada sequência em texto_cifrado, como fizemos no exemplo manual, exceto que desta vez adicionamos algum código para fazer o computador fazer isso programaticamente:
# --- Loop por cada coluna do texto cifrado --- #
for coluna in range(chave):
index_atual = coluna
O loop for itera uma vez para cada coluna, e a variável coluna tem o valor inteiro a ser usado para o índice para texto_cifrado. Na primeira iteração pelo loop for, a variável coluna é definida como 0; na segunda iteração, é definida como 1; depois 2; e assim por diante. Temos o índice para os valores de string em texto_cifrado que queremos acessar mais tarde usando a expressão texto_cifrado[coluna].
Enquanto isso, a variável index_atual mantém o índice para a string de mensagem que o programa olha em cada iteração do loop for. Em cada iteração pelo loop, o código define index_atual para o mesmo valor que a coluna. Em seguida, criaremos o texto cifrado concatenando a mensagem embaralhada, um caractere por vez.
Operadores de atribuição aumentados
Até agora, quando concatenamos ou adicionamos valores uns aos outros, usamos o operador + para adicionar o novo valor à variável. Frequentemente, quando você está atribuindo um novo valor a uma variável, você quer que ele seja baseado no valor atual da variável, então você usa a variável como a parte da expressão que é avaliada e atribuída à variável, como neste exemplo no shell interativo:
n = 30
n = n + 5
print(n)
Existem outras maneiras de manipular valores em variáveis com base no valor atual da variável. Por exemplo, você pode fazer isso usando operadores de atribuição aumentada. A instrução n += 5, que usa o operador de atribuição aumentada +=, faz a mesma coisa que n = n + 5. É apenas um pouco mais curto para digitar. O operador += trabalha com inteiros para fazer adição, strings para fazer concatenação de strings e listas para fazer concatenação de listas. A Tabela 1 mostra os operadores de atribuição aumentada e suas instruções de atribuição equivalentes:
Tabela 1: Operadores de atribuição aumentada.
Usaremos operadores de atribuição aumentados para concatenar caracteres ao nosso texto cifrado.
Movendo index_atual pela mensagem
A variável index_atual contém o índice do próximo caractere na sequência de caracteres de mensagem que será concatenada às listas de texto_cifrado. A chave é adicionada a index_atual em cada iteração do loop while para apontar a caracteres diferentes em mensagem e, em cada iteração do loop for, index_atual é definido como o valor na variável de coluna.
Para embaralhar a string na variável mensagem, precisamos pegar o primeiro caractere de mensagem, que é 'O', e colocá-lo na primeira string de texto_cifrado. Então, pularíamos oito caracteres em mensagem (porque chave é igual a 8) e concatenaríamos esse caractere, que é 'c', à primeira string de texto_cifrado. Continuaríamos pulando caracteres de acordo com a chave e concatenaríamos cada caractere até chegarmos ao fim de mensagem. Isso criaria a string 'Ococ', que é a primeira coluna de texto_cifrado. Então faríamos isso novamente, mas começaríamos no segundo caractere em mensagem para fazer a segunda coluna.
Dentro do loop for há um loop while. Este loop while encontra e concatena o caractere certo em mensagem para formar cada coluna. Ele faz um loop enquanto index_atual for menor que o comprimento de mensagem:
# --- Manter o loop até que index_atual ultrapasse o comprimento da mensagem --- #
while index_atual < len(mensagem):
# --- Colocar o caractere presente na posição do valor index_atual em mensagem no final da coluna atual --- #
texto_cifrado[coluna] += mensagem[index_atual]
# --- Mover o valor de index_atual --- #
index_atual += chave
Para cada coluna, o loop while itera pela variável de mensagem original e seleciona caracteres em intervalos de chave adicionando chave a index_atual. A primeira iteração do loop for, index_atual foi definido como o valor de coluna, que começa em 0.
mensagem[index_atual] é o primeiro caractere de mensagem em sua primeira iteração. O caractere em mensagem[index_atual] é concatenado a texto_cifrado[coluna] para iniciar a primeira coluna. O valor em chave (que é 8) é adicionado a index_atual a cada vez que passa pelo loop. Na primeira vez é mensagem[0], na segunda vez mensagem[8], na terceira vez mensagem[16] e na quarta vez mensagem[24].
Embora o valor em index_atual seja menor que o comprimento da string de mensagem, você deseja continuar concatenando o caractere em mensagem[index_atual] ao final da string no índice da coluna no texto cifrado. Quando index_atual é maior que o comprimento de mensagem, a execução sai do loop while e volta para o loop for. Como não há código no bloco for após o loop while, o loop for itera, coluna é definido como 1 e index_atual começa no mesmo valor que coluna.
Agora, quando adicionamos 8 ao index_atual em cada iteração do loop while, os índices serão 1, 9, 17 e 25.
Como mensagem[1], mensagem[9], mensagem[17] e mensagem[25] são concatenadas ao final de texto_cifrado[1], elas formam a string ' o o'. Esta é a segunda coluna da grade.
Quando o loop for termina de percorrer o restante das colunas, o valor em texto texto_cifrado é ['Ococ', ' o o', 'smém', 'eu m', 'nmtm', 's ã.', 'ono', ' ã ']. Depois de termos a lista de colunas de string, precisamos juntá-las para criar uma string que seja o texto cifrado inteiro: 'Ococ o osmémeu unmtms ã.ono ã '.
O método de string join()
O método join() é usado para unir as strings de colunas individuais de texto_cifrado em uma string. O método join() é chamado em um valor de string e pega uma lista de strings. Ele retorna uma string que tem todos os membros na lista unidos pela string na qual join() é chamado. (Esta é uma string em branco se você quiser apenas unir as strings). Insira o seguinte no shell interativo:
lista = ['pizza', 'macarrão', 'lasanha']
''.join(lista)
', '.join(lista)
'XYZ'.join(lista)
Quando você chama join() em uma string vazia e junta os itens da lista, você obtém as strings da lista concatenadas sem nenhuma string entre elas. Em alguns casos, você pode querer separar cada membro em uma lista para torná-la mais legível, o que fizemos chamando join() na string ', '. Isso insere a string ', ' entre cada membro da lista. Você pode inserir qualquer string que quiser entre os membros da lista.
Valores de retorno e instruções return
Uma chamada de função (ou método) sempre é avaliada como um valor. Este é o valor retornado pela chamada de função ou método, também chamado de valor de retorno da função. Quando você cria suas próprias funções usando uma instrução def, uma instrução return informa ao Python qual é o valor de retorno para a função:
# --- Juntar os itens da lista como uma string só --- #
return ''.join(texto_cifrado)
O final da função chama join() na string em branco e recebe o texto_cifrado como argumento para que as strings na lista de texto_cifrado sejam unidas em uma única string.
Um exemplo de declaração return
Uma instrução return é a palavra-chave return seguida pelo valor a ser retornado. Você pode usar uma expressão em vez de um valor. Quando você faz isso, o valor de retorno é qualquer coisa que a expressão avalia. Crie um novo arquivo e o execute:
def somar_numeros(a, b):
return a + b
print(somar_numeros(30, 5))
Quando você executa o programa o valor mostrado é 35.
Isso ocorre porque a chamada de função somar_numeros(30, 5) é avaliada como 35. A instrução return em somar_numeros() avalia a expressão a + b e então retorna o valor avaliado.
Retornando o texto cifrado criptografado
No nosso programa, a instrução return da função encriptar_mensagem() retorna um valor de string que é criado pela junção de todas as strings na lista de texto_cifrado. Se a lista no texto cifrado for ['Ococ', ' o o', 'smém', 'eu m', 'nmtm', 's ã.', 'ono', ' ã '], a chamada do método join() retornará 'Ococ o osmémeu unmtms ã.ono ã '. Esta string final, o resultado do código de criptografia, é retornada pela nossa função encriptar_mensagem().
A grande vantagem de usar funções é que um programador tem que saber o que a função faz, mas não precisa saber como o código da função funciona. Um programador pode entender que quando ele chama a função encriptar_mensagem() e passa um inteiro, bem como uma string para os parâmetros de chave e mensagem, a chamada da função é avaliada como uma string criptografada. Ele não precisa saber nada sobre como o código em encriptar_mensagem() realmente faz isso, o que é semelhante a como você sabe que quando você passa uma string para print(), ele imprimirá a string mesmo que você nunca tenha visto o código da função print().
A variável __name__
Você pode transformar o programa de criptografia de transposição em um módulo usando um truque especial envolvendo a função main() e uma variável chamada __name__.
Quando você executa um programa Python, __name__ (que são dois sublinhados antes de name e dois sublinhados depois) recebe o valor de string '__main__' (novamente, dois sublinhados antes e depois de main) antes mesmo da primeira linha do seu programa ser executado. O sublinhado duplo é frequentemente chamado de dunder em Python, e __main__ é chamado de dunder main dunder.
No final do arquivo de script (e, mais importante, depois de todas as instruções def), você quer ter algum código que verifique se a variável __name__ tem a string '__main__' atribuída a ela. Se sim, você quer chamar a função main().
A instrução if na é, na verdade, uma das primeiras linhas de código executadas quando você executa o programa (depois das instruções def):
# --- Chamar a função --- #
if __name__ == '__main__':
main()
O motivo pelo qual o código é configurado dessa forma é que, embora o Python defina __name__ como '__main__' quando o programa é executado, ele o define como a string o nome do arquivo em que o código está escrito se o programa for importado por outro programa Python. Semelhante a como o programa importa qualquer módulo para chamar as funções nele, outros programas podem querer importar o programa de cifra para chamar sua função encriptar_mensagem() sem a função main() em execução. Quando uma instrução import é executada, o Python procura o arquivo do módulo adicionando .py ao final do nome do arquivo. É assim que nosso programa sabe se está sendo executado como o programa principal ou importado por um programa diferente como um módulo.
Quando você importa um programa Python e antes que o programa seja executado, a variável __name__ é definida para a parte do nome do arquivo antes de .py. Quando o programa é importado, todas as instruções def são executadas (para definir a função encriptar_mensagem() que o programa importador deseja usar), mas a função main() não é chamada, então o código de criptografia para 'O senso comum não é tão comum.' com a chave 8 não é executado.
É por isso que o código que criptografa a string minha_mensagem com a chave minha_chavem está dentro de uma função (que por convenção é chamada main()). Este código dentro de main() não será executado quando o nosso código for importado por outros programas, mas esses outros programas ainda podem chamar sua função encriptar_mensagem(). É assim que o código da função pode ser reutilizado por outros programas.
Tente você mesmo!
Exercício 01: Com papel e lápis, criptografe as seguintes mensagens com a chave 9 usando a cifra de transposição. O número de caracteres foi fornecido para sua conveniência:
Debaixo de um enorme carvalho havia uma enorme companhia de porcos, (67 caracteres)
Que grunhiram enquanto esmagavam o mastro: Pois estava maduro e caiu rápido. (76 caracteres)
Então eles trotaram para longe, pois o vento estava forte: deixaram apenas uma bolota, e você não conseguiu ver mais nada. (122 caracteres)
Exercício 02: No programa a seguir, cada var é uma variável global ou local?
var = 30
def foo():
global var
var = 99
print(var)
Exercício 03: Qual é o valor de cada uma das seguintes expressões?
[0, 1, 2, 3, 4][2]
[[1, 2], [3, 4]][0]
[[1, 2], [3, 4]][0][1]
['banana'][0][1]
[2, 4, 6, 8, 10][1:3]
list('Olá, mundo!')
list(range(10))[2]
Exercício 04: Qual é o valor de cada uma das seguintes expressões?
len([2, 4])
len([])
len(['', '', ''])
[4, 5, 6] + [1, 2, 3]
3 * [1, 2, 3] + [9]
42 in [41, 42, 42, 42]
Exercício 05: Quais são os quatro operadores de atribuição aumentada?
Resumo
Uau! Você aprendeu vários conceitos novos de programação neste capítulo. O programa de cifra de transposição é mais complicado (mas muito mais seguro) do que o programa de cifra de César no Capítulo 6. Os novos conceitos, funções, tipos de dados e operadores que você aprendeu neste capítulo permitem que você manipule dados de maneiras mais sofisticadas. Apenas lembre-se de que muito do que envolve entender uma linha de código é avaliá-la passo a passo da mesma forma que o Python fará.
Você pode organizar o código em grupos chamados funções, que você cria com instruções def. Valores de argumento podem ser passados para funções como parâmetros da função. Parâmetros são variáveis locais. Variáveis fora de todas as funções são variáveis globais. Variáveis locais são diferentes de variáveis globais, mesmo se tiverem o mesmo nome que a variável global. Variáveis locais em uma função também são separadas de variáveis locais em outra função, mesmo se tiverem o mesmo nome.
Valores de lista podem armazenar vários outros valores, incluindo outros valores de lista. Muitas das operações que você pode usar em strings (como indexação, fatiamento e uso da função len()) podem ser usadas em listas. E operadores de atribuição aumentados fornecem um atalho interessante para operadores de atribuição regulares. O método join() pode unir uma lista que contém várias strings para retornar uma única string.
Talvez seja melhor rever este capítulo se você ainda não estiver confortável com esses conceitos de programação. No Capítulo 8, você aprenderá como decifrar usando a cifra de transposição.