TypeError: 'str' no es compatible con la interfaz del búfer

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) 

El código de Python anterior me está dando el siguiente error:

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
Futuro rey
fuente
1
@ MikePennington: explique por qué comprimir el texto no es útil.
galinette

Respuestas:

295

Si usa Python3x, entonces stringno es del mismo tipo que para Python 2.x, debe convertirlo a bytes (codificarlo).

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'))

Tampoco use nombres de variables como stringo filemientras esos son nombres de módulo o función.

EDITAR @Tom

Sí, el texto no ASCII también está comprimido / descomprimido. Yo uso letras polacas con codificación 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
fuente
Es extraño que esto lo haya solucionado; el código original funcionó para mí en 3.1, y el código de muestra en los documentos tampoco codifica explícitamente. Si lo usa en texto que no es ASCII, ¿gunzip lo descomprime? Tengo un error
Tom Zych
Escribí mi nombre en hindi Unicode y lo comprimió en gzip con éxito. Estoy usando Python 3.2
Future King
@Tom Zych: Probablemente tiene algo que ver con los cambios en 3.2: docs.python.org/dev/whatsnew/3.2.html#gzip-and-zipfile
Skurmedel
Lo probé con ActiveState Python 3.1 y 3.2. En mi máquina funciona en ambos.
Michał Niklas
1
Para la compresión de archivos, siempre debe abrir la entrada en modo binario: debe poder descomprimir el archivo más adelante y obtener exactamente el mismo contenido. La conversión a Unicode ( str) y viceversa es innecesaria, y corre el riesgo de decodificar errores o desajustes entre la entrada y la salida.
alexis
96

Hay una solución más fácil para este problema.

Solo necesita agregar ta al modo para que se convierta wt. Esto hace que Python abra el archivo como un archivo de texto y no como binario. Entonces todo simplemente funcionará.

El programa completo se convierte en esto:

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)
usuario1175849
fuente
¿Funciona también en python2? ¿Podría ser una forma de hacer que el código funcione en python2 y python3?
Loïc Faure-Lacroix
Wow, hombre, eres bueno! ¡Gracias! Déjame votarte. Esta debería ser la respuesta aceptada :))
Loïc
15
Agregar "t" puede tener efectos secundarios. En Windows, los archivos codificados como texto tendrán líneas nuevas ("\ n") convertidas a CRLF ("\ r \ n").
BitwiseMan
42

No puede serializar una 'cadena' de Python 3 a bytes sin una conversión explícita a alguna codificación.

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

Es posiblemente lo que quieres. También esto funciona tanto para python 2.xy 3.x.

Andreas Jung
fuente
28

Para Python 3.x puede convertir su texto a bytes sin procesar a través de:

bytes("my data", "encoding")

Por ejemplo:

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

El objeto devuelto funcionará con outfile.write.

Skurmedel
fuente
9

Este problema ocurre comúnmente cuando se cambia de py2 a py3. En py2 plaintextes tanto una cadena como un tipo de matriz de bytes . En py3 plaintextes solo una cadena , y el método outfile.write()realmente toma una matriz de bytes cuando outfilese abre en modo binario, por lo que se genera una excepción. Cambie la entrada a plaintext.encode('utf-8')para solucionar el problema. Sigue leyendo si esto te molesta.

En AP2, la declaración de file.write hacía parecer como que ha pasado en una cadena: file.write(str). En realidad le estaban pasando en una matriz de bytes, que debería haber sido la lectura de la declaración de la siguiente manera: file.write(bytes). Si lo lees así, el problema es simple, file.write(bytes)necesita un tipo de bytes y en py3 para obtener bytes de un str lo conviertes:

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

¿Por qué los documentos de py2 declararon file.writetomar una cadena? Bueno, en py2 la distinción de declaración no importaba porque:

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

La clase str-bytes de py2 tiene métodos / constructores que hacen que se comporte como una clase de cadena en algunos aspectos y una clase de matriz de bytes en otros. Conveniente para file.writeno es así ?:

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 qué py3 rompió este buen sistema? Bueno, porque en py2 las funciones básicas de cadena no funcionaban para el resto del mundo. ¿Medir la longitud de una palabra con un carácter no 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

Todo este tiempo pensaba que estaba pidiendo a la len de una cadena en AP2, que estaban recibiendo la longitud de la matriz de bytes de la codificación. Esa ambigüedad es el problema fundamental con las clases de doble tarea. ¿Qué versión de cualquier llamada a método implementas?

La buena noticia es que py3 soluciona este problema. Desenreda las clases str y bytes . La clase str tiene métodos similares a cadenas, la clase de bytes separada tiene métodos de matriz de bytes:

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

Con suerte, saber esto ayuda a desmitificar el problema y hace que el dolor de la migración sea un poco más fácil de soportar.

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

Bueno, si es útil para usted en caso de eliminar el molesto carácter 'b'. Si alguien tiene una mejor idea, sugiérame o siéntase libre de editarme en cualquier momento aquí. Solo soy un novato

Tapasit Suesasiton
fuente
También puede usarlo s.encode('utf-8')tan pitónico como s.decode('utf-8')en reemplazo des = bytes("s", "utf-8")
Hans Zimermann
4

Para Djangoen django.test.TestCasela unidad de pruebas, he cambiado de python2 sintaxis:

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

Para usar el Python3 .decode('utf8') sintaxis de :

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