TypeError: 'str' não suporta a interface do buffer

267
plaintext = input("Please enter the text you want to compress")
filename = input("Please enter the desired filename")
with gzip.open(filename + ".gz", "wb") as outfile:
    outfile.write(plaintext) 

O código python acima está me dando o seguinte erro:

Traceback (most recent call last):
  File "C:/Users/Ankur Gupta/Desktop/Python_works/gzip_work1.py", line 33, in <module>
    compress_string()
  File "C:/Users/Ankur Gupta/Desktop/Python_works/gzip_work1.py", line 15, in compress_string
    outfile.write(plaintext)
  File "C:\Python32\lib\gzip.py", line 312, in write
    self.crc = zlib.crc32(data, self.crc) & 0xffffffff
TypeError: 'str' does not support the buffer interface
Rei do Futuro
fonte
1
@ MikePennington: por favor, explique por que a compressão de texto não é útil?
galinette

Respostas:

295

Se você usa o Python3x, então stringnão é o mesmo tipo do Python 2.x, deve convertê- lo em bytes (codificá-lo).

plaintext = input("Please enter the text you want to compress")
filename = input("Please enter the desired filename")
with gzip.open(filename + ".gz", "wb") as outfile:
    outfile.write(bytes(plaintext, 'UTF-8'))

Também não use nomes de variáveis ​​como stringou fileenquanto esses são nomes de módulo ou função.

EDIT @Tom

Sim, o texto não ASCII também é compactado / descompactado. Eu uso letras polonesas com codificação UTF-8:

plaintext = 'Polish text: ąćęłńóśźżĄĆĘŁŃÓŚŹŻ'
filename = 'foo.gz'
with gzip.open(filename, 'wb') as outfile:
    outfile.write(bytes(plaintext, 'UTF-8'))
with gzip.open(filename, 'r') as infile:
    outfile_content = infile.read().decode('UTF-8')
print(outfile_content)
Michał Niklas
fonte
É estranho que isso tenha corrigido; o código original funcionou para mim no item 3.1 e o código de exemplo nos documentos também não codifica explicitamente. Se você o usa em texto não ASCII, o gunzip o descompacta? Eu recebi um erro.
Tom Zych
Digitei meu nome em Unicode Hindi e o compactou no gzip com sucesso. Eu estou usando Python 3.2
Future King
@ Tom Zych: Provavelmente tem algo a ver com as alterações no 3.2: docs.python.org/dev/whatsnew/3.2.html#gzip-and-zipfile
Skurmedel
Eu testei com o ActiveState Python 3.1 e 3.2. Na minha máquina funciona em ambos.
Michał Niklas 29/03
1
Para a compactação de arquivos, você sempre deve abrir a entrada no modo binário: você precisa descompactar o arquivo posteriormente e obter exatamente o mesmo conteúdo. A conversão para Unicode ( str) e vice-versa é desnecessária e corre o risco de decodificar erros ou incompatibilidades entre entrada e saída.
Alexis
96

Existe uma solução mais fácil para esse problema.

Você só precisa adicionar um tao modo para que ele se torne wt. Isso faz com que o Python abra o arquivo como um arquivo de texto e não como binário. Então tudo vai funcionar.

O programa completo passa a ser o seguinte:

plaintext = input("Please enter the text you want to compress")
filename = input("Please enter the desired filename")
with gzip.open(filename + ".gz", "wt") as outfile:
    outfile.write(plaintext)
user1175849
fonte
Também funciona em python2? Poderia ser uma maneira de fazer o código funcionar em python2 e python3?
Loïc Faure-Lacroix
Uau, cara, você é bom! Obrigado! Deixe-me votar em você. Esta deve ser a resposta aceita :))
Loïc
15
Adicionar "t" pode ter efeitos colaterais. No Windows, os arquivos codificados como texto terão novas linhas ("\ n") convertidas em CRLF ("\ r \ n").
precisa saber é o seguinte
42

Você não pode serializar uma 'string' de Python 3 em bytes sem conversão explícita em alguma codificação.

outfile.write(plaintext.encode('utf-8'))

é possivelmente o que você quer. Também funciona para python 2.xe 3.x.

Andreas Jung
fonte
28

Para o Python 3.x, você pode converter seu texto em bytes brutos através de:

bytes("my data", "encoding")

Por exemplo:

bytes("attack at dawn", "utf-8")

O objeto retornado funcionará outfile.write.

Skurmedel
fonte
9

Esse problema geralmente ocorre ao mudar de py2 para py3. Em py2 plaintexthá um tipo de string e um array de bytes . No py3, plaintexté apenas uma string , e o método, outfile.write()na verdade, utiliza uma matriz de bytes quando outfileé aberto no modo binário, portanto, uma exceção é gerada. Mude a entrada paraplaintext.encode('utf-8') para corrigir o problema. Leia se isso lhe incomoda.

Em PY2, a declaração para file.write fez parecer que você passou em uma string: file.write(str). Na verdade você estava passando em um array de bytes, você deve ter lido a declaração como esta: file.write(bytes). Se você lê assim, o problema é simples, file.write(bytes)precisa de um tipo de bytes e, em py3, para obter bytes de um str, você o converte:

py3>> outfile.write(plaintext.encode('utf-8'))

Por que os documentos py2 declararam que file.writelevou uma string? Bem, no py2 a distinção de declaração não importava porque:

py2>> str==bytes         #str and bytes aliased a single hybrid class in py2
True

A classe str-bytes do py2 possui métodos / construtores que fazem com que se comporte como uma classe de strings de algumas maneiras e uma classe de matriz de bytes em outras. Conveniente para file.writenão é ?:

py2>> plaintext='my string literal'
py2>> type(plaintext)
str                              #is it a string or is it a byte array? it's both!

py2>> outfile.write(plaintext)   #can use plaintext as a byte array

Por que o py3 quebrou esse bom sistema? Bem, porque no py2, as funções básicas de string não funcionavam no resto do mundo. Mede o comprimento de uma palavra com um caractere não ASCII?

py2>> len('¡no')        #length of string=3, length of UTF-8 byte array=4, since with variable len encoding the non-ASCII chars = 2-6 bytes
4                       #always gives bytes.len not str.len

Durante todo esse tempo, você pensou que estava pedindo o len de uma string em py2, estava obtendo o comprimento da matriz de bytes da codificação. Essa ambiguidade é o problema fundamental das classes de serviço duplo. Qual versão de qualquer chamada de método você implementa?

A boa notícia é que o py3 corrige esse problema. Desembaraça as classes str e bytes . A classe str possui métodos semelhantes a strings, a classe bytes separados possui métodos de matriz de bytes:

py3>> len('¡ok')       #string
3
py3>> len('¡ok'.encode('utf-8'))     #bytes
4

Espero que isso ajude a desmistificar o problema e torne a dor da migração um pouco mais fácil de suportar.

Riaz Rizvi
fonte
4
>>> s = bytes("s","utf-8")
>>> print(s)
b's'
>>> s = s.decode("utf-8")
>>> print(s)
s

Se você tiver uma idéia melhor, por favor me sugira ou sinta-se à vontade para me editar a qualquer momento aqui.

Tapasit Suesasiton
fonte
Você também pode usar s.encode('utf-8')tão pythônico como s.decode('utf-8')na substituição des = bytes("s", "utf-8")
Hans Zimermann
4

Para Djangonos django.test.TestCasetestes de unidade, eu mudei de python2 sintaxe:

def test_view(self):
    response = self.client.get(reverse('myview'))
    self.assertIn(str(self.obj.id), response.content)
    ...

Para usar a sintaxe do Python3 .decode('utf8') :

def test_view(self):
    response = self.client.get(reverse('myview'))
    self.assertIn(str(self.obj.id), response.content.decode('utf8'))
    ...
Aaron Lelevier
fonte