Ошибка типа: 'str' не поддерживает интерфейс буфера

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) 

Приведенный выше код Python дает мне следующую ошибку:

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
Будущий король
источник
1
@MikePennington: объясните, пожалуйста, почему сжатие текста бесполезно?
Галинет

Ответы:

295

Если вы используете Python3x, то stringэто не тот же тип, что и для Python 2.x, вы должны преобразовать его в байты (закодировать его).

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

Также не используйте имена переменных, такие как stringили fileкогда они являются именами модулей или функций.

РЕДАКТИРОВАТЬ @Tom

Да, не-ASCII текст также сжимается / распаковывается. Я использую польские буквы с кодировкой 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)
Михал Никлас
источник
Странно, что это исправило это; оригинальный код работал для меня под 3.1, и пример кода в документации также не кодируется явно. Если вы используете его в текстах, отличных от ASCII, gunzip распаковывает его? Я получил ошибку.
Том Зыч
Я набрал свое имя на хинди Unicode, и оно успешно сжало его в gzip. Я использую Python 3.2
Future King
@Tom Zych: Вероятно, как-то связано с изменениями в 3.2: docs.python.org/dev/whatsnew/3.2.html#gzip-and-zipfile
Skurmedel
Я протестировал его с ActiveState Python 3.1 и 3.2. На моей машине это работает в обоих.
Михал Никлас
1
Для сжатия файла вы всегда должны открывать ввод в двоичном режиме: вы должны иметь возможность распаковать файл позже и получить точно такой же контент. Преобразование в Unicode ( str) и обратно не требуется , и существует риск декодирования ошибок или несоответствий между вводом и выводом.
Алексис
96

Существует более простое решение этой проблемы.

Вам просто нужно добавить tв режим, чтобы он стал wt. Это заставляет Python открывать файл как текстовый файл, а не как двоичный файл. Тогда все будет просто работать.

Полная программа становится такой:

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
источник
Это работает на python2 тоже? Может ли быть способ заставить код работать на python2 и python3?
Лоик Фор-Лакруа,
Вау, чувак, ты хороший! Спасибо! Позвольте мне проголосовать за вас. Это должен быть принятый ответ :))
Loïc
15
Добавление «т» может иметь побочные эффекты. В файлах Windows, закодированных как текст, символы новой строки ("\ n") будут преобразованы в CRLF ("\ r \ n").
BitwiseMan
42

Вы не можете сериализовать строку Python 3 в байты без явного преобразования в некоторую кодировку.

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

возможно то, что вы хотите. Также это работает для Python 2.x и 3.x.

Андреас Юнг
источник
28

Для Python 3.x вы можете конвертировать ваш текст в необработанные байты через:

bytes("my data", "encoding")

Например:

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

Возвращенный объект будет работать с outfile.write.

Skurmedel
источник
9

Эта проблема обычно возникает при переключении с py2 на py3. В py2 plaintextесть как строка, так и тип байтового массива . В py3 plaintextесть только строка , и метод outfile.write()фактически принимает байтовый массив при outfileоткрытии в двоичном режиме, поэтому возникает исключение. Измените вход, plaintext.encode('utf-8')чтобы решить проблему. Читайте дальше, если это вас беспокоит.

В PY2, то декларация file.write сделал это , похоже , как вы прошли в строке: file.write(str). На самом деле вы проходили в массив байтов, вы должны читали заявления вроде этого: file.write(bytes). Если вы читаете это , как это проблема проста, file.write(bytes)нуждается в байтах типа и в PY3 , чтобы получить байты из ул конвертирование его:

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

Почему py2 docs объявляет, что file.writeвзяла строку? Ну, в py2 различие между объявлениями не имело значения, потому что:

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

У str- байтового класса py2 есть методы / конструкторы, которые в некоторых отношениях заставляют его вести себя как строковый класс, а в других - как класс байтового массива. Удобно для file.writeне так ли?

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

Почему py3 сломал эту замечательную систему? Ну, потому что в py2 основные строковые функции не работали для остального мира. Измерить длину слова с не-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

Все это время вы думали , что вы просили для Len строки в PY2, вы получаете длину массива байт из кодировки. Эта двусмысленность является фундаментальной проблемой для классов с двумя обязанностями. Какую версию любого вызова метода вы реализуете?

Хорошей новостью является то, что py3 решает эту проблему. Он распутывает классы str и bytes . Ул класс имеет нитевидные методы, отдельный байт класс имеет байты методов массива:

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

Надеюсь, знание этого поможет устранить проблему и немного облегчить перенос проблем с миграцией.

Риаз Ризви
источник
4
>>> s = bytes("s","utf-8")
>>> print(s)
b's'
>>> s = s.decode("utf-8")
>>> print(s)
s

Хорошо, если это полезно для вас в случае удаления раздражающего символа 'b'. Если у кого-то есть идея получше, пожалуйста, предложите мне или не стесняйтесь редактировать меня в любое время здесь. Я просто новичок

Tapasit Suesasiton
источник
Вы также можете использовать s.encode('utf-8')его настолько питонно, как s.decode('utf-8')на заменуs = bytes("s", "utf-8")
Ганс Цимерманн
4

Для Djangoв django.test.TestCaseмодульном тестировании, я изменил мой python2 синтаксис:

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

Чтобы использовать синтаксис Python3 .decode('utf8') :

def test_view(self):
    response = self.client.get(reverse('myview'))
    self.assertIn(str(self.obj.id), response.content.decode('utf8'))
    ...
Аарон Лелевье
источник