TypeError: '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той час, як це імена модуля або функції.

EDIT @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
Майбутній король

@Tom Zych: Напевно, щось стосується змін 3.2: docs.python.org/dev/whatsnew/3.2.html#gzip-and-zipfile
Skurmedel

Я перевірив його за допомогою ActiveState Python 3.1 та 3.2. На моїй машині він працює в обох.
Michał Niklas

1
Для стиснення файлів завжди слід відкривати вхід у двійковому режимі: Вам потрібно мати змогу розпакувати файл пізніше та отримати точно той самий вміст. Перетворення в Unicode ( str) і назад не є зайвим, і ризикує розшифрувати помилки або невідповідності між входом і виходом.
alexis

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)

Чи працює він і на python2? Чи може це бути способом змусити код працювати на python2 та python3?
Loïc Faure-Lacroix

Ого, чоловіче, ти добрий! Дякую! Дозвольте мені проголосувати. Це має бути прийнята відповідь :))
Loïc

15
Додавання "t" може мати побічні ефекти. У файлах 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.


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 оголосили, що file.writeвзяли рядок? Ну а в py2 відмінність декларації не мала значення, оскільки:

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

Клас str-bytes 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 . Клас str має рядкоподібні методи, окремий клас байтів має методи байтового масиву:

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". Якщо хтось має кращу ідею, будь ласка, підкажіть мені або не соромтесь редагувати мене будь-коли тут. Я просто новачок


Ви також можете використовувати 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'))
    ...
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.