post

Python pt-br:Resolucao de Problemas

Nós exploramos várias partes da linguagem Python e agora daremos uma olhada em como essas partes se ajustam entre si, projetando e escrevendo um programa que faz algo útil. A idéia é aprender a escrever o seu próprio script em Python.

Contents

O Problema

O problema é “Eu desejo escrever um programa que cria um backup de todos os meus arquivos importantes”

Embora esse seja um problema simples, não existe informação suficiente para que possamos dar início a uma solução. Um pouco mais de análise é necessária. Por exemplo, como poderíamos especificar quais os arquivos cujo backup deveria ser feito? Como eles estão alocados? Onde eles estão alocados?

Depois de analisarmos apropriadamente o problema, nós projetamos o nosso programa. Nós elaboramos uma lista de coisas a respeito de como o nosso programa deveria operar. Neste caso em particular, criei a lista que se segue, com base em como eu quero que ele funcione. Se você desenvolver o projeto, poderá surgir com outro tipo de análise, uma vez que cada pessoa tem o seu próprio modo de fazer as coisas, o que é perfeitamente legítimo.

  1. Os arquivos e diretórios objeto do backup estão especificados em uma lista.
  2. O backup deve ser colocado em um diretório principal de backup.
  3. O backup deve ser colocado em uma arquivo do tipo zip.
  4. O nome do arquivo zip é a data e a hora correntes.
  5. Usaremos o comando zip disponível por default em qualquer distribuição Linux/Unix padrão. Os usuários de Windows podem instalar da página do projeto e adicionar C:Arquivos de ProgramasGnuWin32bin a variável de ambiente do sistema PATH, de modo análogo ao que fizemos para que o comando python fosse reconhecido. Note que você pode utilizar qualquer comando de arquivamento que desejar, desde que possua uma interface através de linha de comando, de forma que possamos passar argumentos a ele por meio de nosso script.

A Solução

Uma vez que o projeto de nosso programa encontra-se agora razoavelmente estável, podemos escrever o código que representa uma implementação de nossa solução.

#!/usr/bin/python
# Nome do arquivo: backup_ver1.py

import os
import time

# 1. Os arquivos e os diretórios a sofrerem backup estão especificados em uma lista.
origem = ['"C:\Meus Documentos"', 'C:\Code']
# Note que nós tivemos que usar aspas duplas dentro da string devido aos nomes com espaços dentro dela. 

# 2. O backup deve ser colocado em um diretório de backup principal.
dir_alvo = 'E:\Backup' # Lembre-se de alterar para aquele que você estiver utilizando.

# 3. O backup deve ser colocado em uma arquivo do tipo zip. 
# 4. O nome do arquivo zip é a data e a hora correntes.
alvo = dir_alvo + os.sep + time.strftime('%Y%m%d%H%M%S') + '.zip'

# 5. Usamos o comando zip para colocar os arquivos em um arquivo zip.
comando_zip = "zip -qr {0} {1}".format(alvo, ' '.join(origem))

# Execute o backup 
if os.system(comando_zip) == 0:
    print('Backup bem-sucedido em', alvo)
else:
    print('Backup FALHOU')

Saída:

  $ python backup_ver1.py
  Backup bem-sucedido em E:Backup20080702185040.zip

Agora, nós nos encontramos em uma fase de testes, quando nós verificamos que o nosso programa funciona de forma apropriada. Se ele não se comportar como esperado, então nós temos que debugar o nosso programa, ou seja, remover os bugs (erros) do programa.

Se o programa acima não funcionar para você, coloque um print(comando zip) imediatamente antes da chamada para os.system e execute o programa. Agora copie/cole o comando_zip impresso no ‘prompt’ do shell e veja se ele executa adequadamente por si mesmo. Se o comando falhar, verifique no manual do comando zip o que poderia estar errado. Se o comando for bem sucedido, então verifique se o programa em Python corresponde exatamente ao prograna escrito acima.

Como funciona:

Você notará como nós convertemos o nosso projeto em um procedimento passo-a-passo.

Nós fazemos uso dos módulos os e time, antes de tudo importando-os. Então, nós especificamos os arquivos e diretórios cujo backup deve ser realizado na lista tt>origem</tt>. O diretório alvo é onde nós armazenamos todos os arquivos do backup e este está especificado pela variável dir_alvo. O nome do arquivo zip que iremos criar é a data e hora correntes, os quais nós obtemos utilizando a função time.strftime(). Ele receberá também a extensão zip e será posto no diretório dir_alvo.

Observe o uso da variável os.sep – esta fornece o separador de diretórios de acordo com o sistema operacional, ou seja, será '/' em Linux, Unix, será '\' em Windows e ':' em Mac OS. O uso de os.sep, ao invés desses caracteres diretamente, tornará o nosso programa portável e capaz de operar sob aqueles sistemas.

A função time.strftime() recebe uma especificação tal como a que usamos no programa acima. A especificação %Y será substituída pelo ano sem o século. A especificação %m será substituída pelo mes como um número decimal entre 01 e 12 e assim por diante. A lista completa de tais especificações pode ser encontrada no Python Reference Manual(Manual de Referência de Python).

Nós criamos o nome do arquivo alvo zip utilizando o operador adição, o qual concatena as strings, isto é, combina conjuntamente as strings e retorna uma nova string. Então, nós criamos uma string comando_zip que contém o comando que iremos executar. Você pode verificar se esse comando funciona, executando-o no shell (terminal de Linux ou no ‘prompt’ do DOS).

Ao comando zip que nós estamos usando, são passadas algumas opções e parâmetros. A opção The -q é empregada para indicar que o comando zip deve operar de forma silenciosa (quiet). A opção -r especifica que o comando zip deve trabalhar recursivamente sobre os diretórios, ou seja, deverá incluir todos os subdiretórios e arquivos. As duas opções são combinadas e especificadas como -qr. As opções são seguidas pelo nome de um arquivo zip a ser criado seguido pela lista de arquivos e diretórios a realizar o backup. Nós convertemos a lista origem em uma string, usando o método join de strings, o qual já vimos como empregar.

Então, nós finalmente executamos o comando usando a função os.system, que executa o comando como se o estivesse fazendo pelo sistema operacional, isto é, no shell – ele retorna 0 se o comando foi bem sucedido, retornando um número de erro, em caso contrário.

Dependendo do resultado do comando, nós imprimimos a mensagem apropriada se o backup falhou ou se foi bem sucedido.

Então é isso, nós criamos um script para realizar um backup de nossos arquivos importantes!

Nota aos Usuários de Windows
Ao invés de sequências de escape com duplo ‘backslash’ você pode também eepregar raw strings. Por exemplo, use 'C:\Documentos' ou r'C:Documentos'. Entretanto, não utilize 'C:Documentos', uma vez que você vai acabar encontrando uma sequência desconhecida de escape D.

Agora que nós temos um script de backup operacional, podemos usá-lo em qualquer situação em que desejamos obter um backup de arquivos. Os usuários de Linux/Unix são aconselhados a empregar o metodo_executavel como discutido anteriormente de modo que possam executar o script de backup a qualquer momento e em qualquer lugar. Esta é chamada a fase de operação ou de emprego do software.

O programa acima funciona apropriadamente, mas (usualmente) os primeiros programas não operam exatamente como você espera. Por exemplo, poderiam haver problemas se você não tivesse projetado o programa adequadamente ou se você tivesse cometido um erro ao digitar o código, etc. Apropriadamente, você terá que voltar à fase de projeto ou terá que ‘debugar’ o seu programa.

Segunda Versão

A primeira versão do nosso script funciona. Entretanto, nós podemos implementar certos refinamentos a ele, de modo a que possa funcionar ainda melhor dia após dia. Esta é chamada a fase de manutenção do software.

Um dos refinamentos que eu achei que seria útil é um mecanismo mais adequado de atribuição do nome para os arquivos – usar a hora como o nome do arquivo dentro de um diretório com a data corrente como um diretório no diretório principal de backup. A primeira vantagem é que os seus backups estarão armazenados de uma forma hierárquica e assim muito mais fácil de gerenciar. A segunda vantagem é que o comprimento dos nomes dos arquivos será muito menor. A terceira vantagem é que diretórios separados o ajudarão a facilmente verificar se você já realizou o backup do dia, uma vez que o diretório só será criado se você tiver efetuado o backup daquele dia.

#!/usr/bin/python
# Nome do arquivo: backup_ver2.py

import os
import time

# 1. Os arquivos e os diretórios cujo backup será efetuado estarão especificados em uma lista.
origem = ['"C:\Meus Documentos"', 'C:\Code']
# Note que nós tivemos que usar aspas duplas dentro da string devido aos nomes com espaços dentro dela.

# 2. O backup deve ser colocado em um diretório de backup principal.
dir_alvo = 'E:\Backup' # Lembre-se de alterar para aquele que você estiver utilizando.

# 3. O backup deve ser colocado em uma arquivo do tipo zip.
# 4. O dia corrente é o nome do subdiretório no diretório principal.
hoje = dir_alvo + os.sep + time.strftime('%Y%m%d')
# A hora corrente é o nome do arquivo zip
agora = time.strftime('%H%M%S')

# Crie o diretório se ainda não estiver lá
if not os.path.exists(hoje):
    os.mkdir(hoje) # crie diretório
    print('Bem-Sucedida Criação do Diretório', hoje)

# O nome do arquivo zip
alvo = hoje + os.sep + agora + '.zip'

# 5. Usamos o comando zip para colocar os arquivos em um arquivo zip
comando_zip = "zip -qr {0} {1}".format(alvo, ' '.join(origem))

# Execute o backup
if os.system(comando_zip) == 0:
    print('Backup Bem-Sucedido em', alvo)
else:
    print('Backup FALHOU')

Saída:

  $ python backup_ver2.py
  Bem-Sucedida Criação do Diretório E:Backup20080702
  Backup Bem-Sucedido em E:Backup20080702202311.zip

  $ python backup_ver2.py
  Backup Bem-Sucedido em E:Backup20080702202325.zip

Como funciona:

A maior parte do programa permanece a mesma. As alterações são aquelas nas quais verificamos se existe um diretório com o dia corrente como nome dentro do diretório principal utilizando a função os.exists. Se não existir, nós o criamos por meio da função os.mkdir.

Terceira Versão

A segunda versão funciona muito bem quando eu efetuo muitos backups, mas quando há um número muito grande de backups, eu estou achando difícil diferenciar para que razão são os backups! Por exemplo, eu poderia ter feito algumas alterações significativas a um programa ou apresentação, então eu gostaria a razão para aquelas alterações ao nome do arquivo zip. Isso pode ser facilmente obtido associando-se um comentário fornecido pelo usuário ao nome do arquivo zip.

Nota
O programa a seguir não funciona, então não precisa ficar alarmado, por favor acompanhe a discussão, pois nela existe uma lição.
#!/usr/bin/python
# Nome do arquivo: backup_ver3.py

import os
import time

# 1. Os arquivos e os diretórios cujo backup será efetuado estarão especificados em uma lista.
origem = ['"C:\Meus Documentos"', 'C:\Code']
# Note que nós tivemos que usar aspas duplas dentro da string devido aos nomes com espaços dentro dela.

# 2. O backup deve ser colocado em um diretório de backup principal.
dir_alvo = 'E:\Backup' # Lembre-se de alterar para aquele que você estiver utilizando.

# 3. O backup deve ser colocado em uma arquivo do tipo zip.
# 4. O dia corrente é o nome do subdiretório no diretório principal.
hoje = dir_alvo + os.sep + time.strftime('%Y%m%d')
# A hora corrente é o nome do arquivo zip
agora = time.strftime('%H%M%S')

# Receba um comentário do usuário para criar o nome do arquivo zip
coment = input('Entre com um comentário --> ')
if len(coment) == 0: # verifique se entrou um comentário
    alvo = hoje + os.sep + agora + '.zip'
else:
    alvo = hoje + os.sep + agora + '_' +
        coment.replace(' ', '_') + '.zip'

# Crie o diretório se ainda não estiver lá
if not os.path.exists(hoje):
    os.mkdir(hoje) # crie diretório
    print('Bem-Sucedida Criação do Diretório', hoje)

# 5. Usamos o comando zip para colocar os arquivos em um arquivo zip
comando_zip = "zip -qr {0} {1}".format(alvo, ' '.join(origem))

# Execute o backup
if os.system(comando_zip) == 0:
    print('Backup Bem-Sucedido em', alvo)
else:
    print('Backup FALHOU')

Saída:

  $ python backup_ver3.py
    File "backup_ver3.py", line 25
      alvo = hoje + os.sep + agora + '_' +
                                          ^
  SyntaxError: invalid syntax

Como (não) funciona:

Este programa não funciona! Python está dizendo que existe um erro de sintaxe o que significa que o script não satisfaz a estrutura que Python espera encontrar. Quando nós observamos o erro dado por Python, ele também nos aponta o local onde detetou o erro. Então nós começamos a debugar o nosso programa a partir daquela linha.

Mediante uma observação mais cuidadosa, nós verificamos que a única linha lógica foi dividida em duas linhas físicas, mas nós não especificamos que essas duas linhas físicas estão conectadas uma a outra. Basicamente, Python encontrou o operador de adição (+) sem qualquer operando naquela linha lógica e daí não sabe como prosseguir. Lembre-se que nós podemos especificar que a linha lógica continua na próxima linha física através do uso de um ‘backslash’ no final da linha física. Assim, nós efetuamos essa correção no nosso programa. A correção do programa quando nós encontramos erros é denominada conserto de bugs.

Quarta Versão

#!/usr/bin/python
# Nome do arquivo: backup_ver4.py

import os
import time

# 1. Os arquivos e os diretórios cujo backup será efetuado estarão especificados em uma lista.
origem = ['"C:\Meus Documentos"', 'C:\Code']
# Note que nós tivemos que usar aspas duplas dentro da string devido aos nomes com espaços dentro dela.

# 2. O backup deve ser colocado em um diretório de backup principal.
dir_alvo = 'E:\Backup' # Lembre-se de alterar para aquele que você estiver utilizando.

# 3. O backup deve ser colocado em uma arquivo do tipo zip.
# 4. O dia corrente é o nome do subdiretório no diretório principal.
hoje = dir_alvo + os.sep + time.strftime('%Y%m%d')
# A hora corrente é o nome do arquivo zip
agora = time.strftime('%H%M%S')

# Receba um comentário do usuário para criar o nome do arquivo zip
coment = input('Entre com um comentário --> ')
if len(coment) == 0: # verifique se entrou um comentário
    alvo = hoje + os.sep + agora + '.zip'
else:
    alvo = hoje + os.sep + agora + '_' +
        coment.replace(' ', '_') + '.zip'

# Crie o diretório se ainda não estiver lá
if not os.path.exists(hoje):
    os.mkdir(hoje) # crie diretório
    print('Bem-Sucedida Criação do Diretório', hoje)

# 5. Usamos o comando zip para colocar os arquivos em um arquivo zip
comando_zip = "zip -qr {0} {1}".format(alvo, ' '.join(origem))

# Execute o backup
if os.system(comando_zip) == 0:
    print('Backup Bem-Sucedido em', alvo)
else:
    print('Backup FALHOU')

Saída:

  $ python backup_ver4.py
  Entre com um comentário --> novos exemplos adicionados
  Backup Bem-Sucedido em E:Backup20080702202836_novos exemplos_adicionados.zip

  $ python backup_ver4.py
  Entre com um comentário -->
  Backup Bem-Sucedido em E:Backup20080702202839.zip

Como funciona:

Este programa agora funciona! Vamos discutir os melhoramentos reais que fizemos na versão 3. Nós coletamos o comentário do usuário usando a função input e então verificamos se o usuário efetivamente entrou com alguma coisa, por meio da determinação do comprimento da entrada usando a função len. Se o usuário apenas pressionou enter sem realmente haver entrado com algo (talvez tenha sido apenas um backup de rotina ou não haviam mudanças significativas), então nós procedemos como havíamos feito antes.

Entretanto, se um comentário foi fornecido, então ele é associado ao nome do arquivo imediatamente antes da extensão .zip. Observe que nós estamos substituindo espaços nos comentários por ‘underscores’ (sublinhados) – isto porque gerenciar arquivos sem espaços é muito mais fácil.

Mais Refinamentos

A quarta versão é um script que funciona satisfatoriamente para a maior parte dos usuários, mas existe espaço para melhorias. Por exemplo, você poderia incluir um nível de verbosidade para o programa, no qual você poderia especificar uma opção -v para tornar o seu programa mais informativo.

Uma outra possível melhoria seria permitir arquivos e diretórios adicionais a serem passados ao script na linha de comando. Nós podemos obter esses nomes da lista sys.argv e adicioná-los a nossa lista sorigem utilizando o método extend da classe list.

O refinamento mais importante seria a não utilização da via através de os.system para a criação dos arquivos e ao invés disso lançar mão dos módulos ‘built-in’ zipfile ou tarfile para criá-los. Estes são parte da biblioteca padrão e prontamente disponíveis para seu uso sem as dependências externas do programa zip que deve estar disponível no seu computador.

Entretanto, eu usei o caminho de os.system para criar um backup nos exemplos acima por razões puramente pedagógicas, de modo que o exemplo fosse suficientemente simples para ser entendido por qualquer pessoa mas real o suficiente para ser útil.

Você poderia tentar escrever a quinta versão que utiliza o módulo zipfile ao invés do recurso ao os.system?

O Processo de Desenvolvimento de Software

Nós passamos pelas várias fases no processo de escrever um software. Essas fases podem ser resumidas como segue:

  1. Descubra o Quê (Análise)
  2. Saiba Como (Projeto)
  3. Faça (Implementação)
  4. Teste (Testando and ‘Debugando’)
  5. Use (Operação or Emprego)
  6. Mantenha (Refinamento)

Uma forma recomendada de escrever programas é o procedimento que nós seguimos ao criar o script de backup: Realize a análise e o projeto. Comece por implementar uma versão mais simples. Teste-a e conserte os seus ‘bugs’. Use a versão para certificar-se que opera como esperada. Agora, acrescente algumas funcionalidades que você queira e continue a repetir o ciclo Faça-Teste-Use tantas vezes quanto necessário. Lembre-se, Software é desenvolvido, não vem pronto.

Resumo

Nós vimos como criar os nossos próprios programas/scripts em Python e os vários estágios envolvidos ao escrever esses programas. Você poderá achar útil criar o seu pŕoprio programa da mesma maneira que nós fizemos nesse capítulo, de modo que você se fique à vontade não apemnas com Python, mas também com a resolução de problemas.

A seguir, discutiremos a programação orientada a objetos.