Manipulando strings como se fossem arquivos – StringIO

Há tempos atrás eu precisei extrair dados de pagamentos de arquivos pdf, mas a API que eu estava utilizando para extrair os dados do pdf trabalhava exclusivamente com arquivos. Isto é, a função que convertia o pdf em texto precisava receber um arquivo como argumento, para nele escrever o texto extraído do pdf. Entretanto, criar um arquivo (mesmo que temporário), escrever nele, e por fim, ler os dados que me interessavam a partir dele, não era algo muito conveniente, afinal, tudo que eu precisava eram os dados dos pagamentos para efetivar alguns registros no banco de dados. Foi aí que conheci o StringIO.

StringIO é uma classe Python que representa strings em estruturas que se comportam como se fossem arquivos (com a mesma interface dos objetos file), mas que ficam residentes em memória, como strings comuns. Isso pode ser útil quando lidamos com APIs cuja interface exige objetos file.

Para ter uma ideia melhor do que é a StringIO, veja o exemplo abaixo:

Importante: em Python 3.x, a classe StringIO foi movida para o módulo io (dica do alansteixeira). Para usá-la, faça: from io import StringIO

>>> from StringIO import StringIO
>>> fp = StringIO("uma string qualquer")
>>> fp.readline()
'uma string qualquer'
>>> fp.readline()
''

Também podemos criar um objeto StringIO, passar para alguma função escrever algo nele, e então obter os valores escritos chamando o método getvalue().

>>> fp = StringIO()
>>> def func(f):
>>>     f.write("hello")
>>> func(fp)
>>> fp.getvalue()
'hello'

Quando criamos uma API, às vezes é necessário fornecer uma mesma funcionalidade através de duas funções que diferem nos tipos dos parâmetros esperados: uma recebendo um arquivo e outra recebendo uma string. Isso acontece em algumas APIs conhecidas, como a de manipulação de JSON, que fornece as funções load()/dump() e loads()/dumps(), onde as primeiras recebem um arquivo como parâmetro e as últimas recebem uma string. O que uma classe como a StringIO nos permite fazer é implementar a lógica da função somente na função que recebe o arquivo. Dessa forma, a função que recebe uma string pode empacotá-la em um objeto StringIO e então chamar a função que recebe um arquivo, passando o objeto em questão para ela.

Uma outra coisa interessante que podemos fazer com a StringIO é interceptar a saída do programa que iria para a stdout (descobri isso aqui: http://effbot.org/librarybook/stringio.htm). Para fazer isso, temos que substituir a saída padrão (acessível via sys.stdout) por um objeto StringIO. Assim, tudo que for impresso via print será gravado em um objeto StringIO e não na saída-padrão. Veja:

# -*- encoding:utf-8 -*-
import sys
from StringIO import StringIO

backup_stdout = sys.stdout
sys.stdout = StringIO()

# será escrito em um StringIO
print "hello world"

# pega uma referência ao objeto StringIO antes de restaurar a stdout
fp = sys.stdout
sys.stdout = backup_stdout
print "stdout: " + fp.getvalue()

E, assim como fizemos com a stdout, poderíamos ter feito com a stderr (saída-padrão de erros), para interceptar as mensagens de erro e, quem sabe, analisá-las.

Enfim, sempre que se deparar com uma API que exige arquivos como parâmetros, lembre-se da StringIO caso não esteja a fim de acessar o disco e de lidar com arquivos.

Anúncios

O módulo getpass

Embora não seja um grande problema para quem desenvolve aplicativos web ou que utilizem algum outro tipo de GUI (graphical user interface), quando escrevemos um aplicativo que irá operar somente em modo texto, ficamos na dúvida de como fazer para ler do teclado um campo como a senha do usuário. Se utilizarmos uma função como raw_input(), cada caractere que o usuário digitar irá aparecer, deixando assim a senha visível no terminal. Por exemplo:

username = raw_input('username: ')
password = raw_input('password: ')
print 'OK.'

Ao executar um programa que tenha as linhas de código acima para ler nome do usuário e senha do teclado, irá acontecer o seguinte:

username: teste
password: minhasenha123
OK.

Como podemos ver, fica muito ruim, afinal a senha do usuário fica exposta. A solução para que o usuário possa digitar sua senha sem que ela apareça (seja ecoada) na tela pode ser encontrada no módulo getpass [1]. Esse módulo fornece duas funções:

  • getpass(): apresenta ao usuário um prompt solicitando a sua senha e não ecoa na tela os caracteres digitados por ele.
  • getuser(): retorna o username do usuário atual no sistema (para Linux e Windows).

O uso de ambas as funções é bem simples. A função getpass() pode receber um argumento que é um texto que será apresentado ao usuário como prompt. Teste o seguinte código:

import getpass
username = getpass.getuser()
password = getpass.getpass('Digite sua senha: ')

Como você verá, será apresentado ao usuário a mensagem “Digite sua senha: “, com o cursor ao lado esperando pela entrada do usuário. Ao digitar a senha, o cursor permanece parado, não dando indicativo visual algum sobre a senha digitada.

É isso. Se precisar escrever um programa que leia do teclado a senha do usuário e não quer que ela seja ecoada, use o módulo getpass.

 

[1] http://docs.python.org/library/getpass.html

Calculando o hash de strings em Python

Usamos o termo hashing para nos referirmos a uma técnica muito utilizada em programação, quando queremos garantir determinadas propriedades sobre os dados que estamos manipulando e transmitindo. Obter o hash de um conjunto de dados significa obter uma string de tamanho fixo que é calculada com base no conteúdo do conjunto de dados.

Por exemplo, o hash (calculado usando o algoritmo md5) da palavra “teste” é mostrado abaixo:

"teste" --> "1ceae7af21732ab80f454144a414f2fa"

Uma mínima modificação na string “teste” irá gerar um hash totalmente diferente:

"testa" --> "9dd18b1a48164eaec9979df1a6aa84aa"

O hashing é muito utilizado para verificar a integridade de um arquivo durante a transmissão deste pela rede. Ao disponibilizar um arquivo para download, uma empresa pode disponibilizar também o hash calculado sobre o arquivo. Assim, para ter certeza de que efetuou o download do arquivo correto (sem este ter sido corrompido), o usuário pode calcular o hash do arquivo recebido e comparar o hash calculado com o hash disponibilizado pela empresa. Se houverem diferenças entre os hashes, isso significa que o arquivo está corrompido.

hashlib

Python oferece um módulo chamado hashlib que fornece funções para cálculo de hash de dados. Como usá-lo?

import hashlib
h = hashlib.md5()
h.update("uma frase qualquer")
print h.hexdigest()

O código acima utiliza a função md5 para obter o hash da string “uma frase qualquer” e imprimí-lo utilizando o valor do hash obtido em hexadecimal. Além do md5, a hashlib implementa os seguintes algoritmos para cálculo de hash:

  • md5
  • sha1
  • sha224
  • sha256
  • sha384
  • sha512

Assim, para utilizar a função sha256, basta fazer:

import hashlib
h = hashlib.sha256()
h.update("uma frase qualquer")
print h.hexdigest()

Maiores informações podem ser obtidas na documentação oficial: http://docs.python.org/library/hashlib.html

Acentuação em programas Python

Python, por padrão, interpreta seus programas usando a codificação de caracteres ASCII. Tal codificação não é capaz de representar os caracteres acentuados (á, á, â, ã, …), que são muito utilizados na nossa língua portuguesa. Então, como escrever um comentário no código utilizando a grafia correta, sem precisar escrever ‘é’ como ‘eh’? Como escrever mensagens a serem passadas ao usuário utilizando acentuação nos caracteres? Como evitar que a mensagem de erro abaixo apareça como saída do programa?

File "foo.py", line 2
SyntaxError: Non-ASCII character '\xc3' in file foo.py on line 2, but no
encoding declared;see http://www.python.org/peps/pep-0263.html for details

É preciso indicar ao Python qual é a codificação de caracteres que nosso arquivo de código está utilizando. Isso pode ser feito incluindo a seguinte linha de código no cabeçalho do arquivo.

# coding=<nome da codificação>

ou:

# -*- coding: <nome da codificação> -*-

As distribuições Linux, em sua maioria, utilizam a codificação UTF-8 para representação de caracteres. Assim, para utilizar caracteres com acentuação em um arquivo Python, usando Linux, é preciso adicionar ao começo* do arquivo:

# coding=UTF-8

ou:

# -*- coding: UTF-8 -*-

* A linha que indica a codificação pode ser a primeira linha do arquivo, ou a segunda, caso o arquivo contenha a indicação do binário do interpretador na primeira linha. Ex.:

#!/usr/bin/python
# -*- coding: UTF-8 -*-

Mais informações em: http://www.python.org/dev/peps/pep-0263

docstrings

Docstrings são strings que inserimos dentro de nosso código Python com o intuito de fornecer uma explicação sobre o funcionamento deste. Essa string deve ser colocada como a primeira linha da definição de uma classe, método ou função.

O texto representado por tal string será apresentado quando for executado o comando help() utilizando como entrada a função onde a docstring está inserida. Considere o exemplo da função dobro:

def dobro(x):
    """Esta função retorna o dobro do número x"""
    return x*2

Quando executarmos o comando help sobre a função acima, receberemos o conteúdo da string colocada como a primeira linha:

>>> help(dobro)
Help on function dobro in module __main__:

dobro(x)
    Esta função retorna o dobro do número x

A string inserida como docstring também pode ser acessada através do atributo __doc__ daquela função:

>>> print dobro.__doc__
Esta função retorna o dobro do número x

Isso vale também para classes e métodos:

 class Data:
     """Classe utilizada para a representação de datas.
     As datas são representadas no formato dia, mês e ano.
     """
     ...
     def passaDia(self):
         """Acrescenta um dia na data."""
         ...

Ao invocarmos o comando help sobre a classe Data, obteremos o seguinte resultado:

>>> help(Data)
Help on class Data in module __main__:

class Data
 |  Classe utilizada para a representação de datas.
 |  As datas são representadas no formato dia, mês e ano.
 | 
 |  Methods defined here:
 | 
 |  passaDia(self)
 |      Acrescenta um dia na data.

Dúvidas sobre o que e como colocar em uma docstring? Consulte a PEP 257 (PEP é a sigla referente a: Python Enhancement Proposals).