TypeError: „str” nie obsługuje interfejsu bufora

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) 

Powyższy kod python daje mi następujący błąd:

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
Przyszły król
źródło
1
@MikePennington: wyjaśnij, dlaczego kompresja tekstu nie jest przydatna?
galinette

Odpowiedzi:

295

Jeśli używasz Python3x, stringto nie jest tego samego typu, co Python 2.x, musisz przerzucić go na bajty (zakodować).

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

Nie używaj także nazw zmiennych, takich jak stringlub, filegdy są to nazwy modułów lub funkcji.

EDYCJA @ Tom

Tak, tekst spoza ASCII jest również kompresowany / dekompresowany. Używam polskich liter z kodowaniem 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
źródło
Dziwne, że to naprawiło; oryginalny kod działał dla mnie pod 3.1, a przykładowy kod w dokumentach również nie koduje jawnie. Jeśli użyjesz go w tekście innym niż ASCII, czy Gunzip go rozpakuje? Dostałem błąd.
Tom Zych
Wpisałem swoje imię w języku Unicode Hindi i skompresowałem je z powodzeniem w gzip. Używam Python 3.2
Future King
@Tom Zych: Prawdopodobnie ma coś wspólnego ze zmianami w 3.2: docs.python.org/dev/whatsnew/3.2.html#gzip-and-zipfile
Skurmedel
Testowałem to z ActiveState Python 3.1 i 3.2. Na mojej maszynie działa w obu przypadkach.
Michał Niklas
1
W celu kompresji plików należy zawsze otwierać dane wejściowe w trybie binarnym: Musisz później móc rozpakować plik i uzyskać dokładnie taką samą zawartość. Konwersja na Unicode ( str) i wstecz jest niepotrzebna i grozi błędami dekodowania lub niedopasowaniem danych wejściowych i wyjściowych.
Alexis
96

Istnieje łatwiejsze rozwiązanie tego problemu.

Musisz tylko dodać ttryb, aby stał się wt. Powoduje to, że Python otwiera plik jako plik tekstowy, a nie binarny. Wtedy wszystko po prostu zadziała.

Cały program wygląda następująco:

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)
użytkownik1175849
źródło
Czy to działa również na python2? Czy może to być sposób, aby kod działał na python2 i python3?
Loïc Faure-Lacroix,
Wow, stary, jesteś dobry! Dzięki! Pozwól mi zagłosować. To powinna być zaakceptowana odpowiedź :))
Loïc
15
Dodanie „t” może mieć skutki uboczne. W systemie Windows pliki zakodowane jako tekst będą miały znaki nowej linii („\ n”) przekonwertowane na CRLF („\ r \ n”).
BitwiseMan
42

Nie można serializować „łańcucha” Python 3 do bajtów bez wyraźnej konwersji na niektóre kodowania.

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

jest prawdopodobnie tym, czego chcesz. Działa to również zarówno dla Pythona 2.x, jak i 3.x.

Andreas Jung
źródło
28

W przypadku Python 3.x możesz przekonwertować tekst na surowe bajty poprzez:

bytes("my data", "encoding")

Na przykład:

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

Zwrócony obiekt będzie działał z outfile.write.

Skurmedel
źródło
9

Ten problem często występuje podczas przełączania z py2 na py3. W py2 plaintextjest zarówno ciąg znaków, jak i tablica bajtów . W py3 plaintextjest tylko ciąg znaków , a metoda outfile.write()faktycznie pobiera tablicę bajtów, gdy outfilejest otwierana w trybie binarnym, więc zgłaszany jest wyjątek. Zmień dane wejściowe, plaintext.encode('utf-8')aby rozwiązać problem. Czytaj dalej, jeśli ci to przeszkadza.

W Py2 The zgłoszenie o file.write wykonane wydawać się zdałeś w ciąg: file.write(str). Właściwie mijały w tablicy bajtów, trzeba było czytać deklarację takiego: file.write(bytes). Jeśli czytasz to tak, że problem jest prosty, file.write(bytes)potrzebuje bajtów typ oraz w py3 aby uzyskać bajty z pomocą str go przekonwertować:

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

Dlaczego dokumentacja py2 zadeklarowała file.writeciąg znaków? Dobrze w py2 rozróżnienie deklaracji nie miało znaczenia, ponieważ:

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

Klasa str-bytes py2 ma metody / konstruktory, które sprawiają, że pod pewnymi względami zachowuje się jak klasa ciągów, a klasa tablicy bajtów w innych. Wygodne, file.writeprawda ?:

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

Dlaczego py3 złamał ten ładny system? Cóż, ponieważ w py2 podstawowe funkcje łańcuchowe nie działały dla reszty świata. Zmierzyć długość słowa ze znakiem innym niż 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

Przez cały ten czas myślałeś, że pytasz o długość łańcucha w py2, uzyskiwałeś długość tablicy bajtów z kodowania. Ta dwuznaczność jest podstawowym problemem w przypadku klas podwójnego obciążenia. Którą wersję dowolnego wywołania metody implementujesz?

Dobra wiadomość jest taka, że ​​py3 rozwiązuje ten problem. Rozplątuje klasy str i bajty . Str klasa ma sznuropodobne sposobach oddzielne bajtów klasa ma bajt metody tablicy:

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

Mam nadzieję, że wiedza o tym pomoże w tajemnicy problemu i sprawi, że ból związany z migracją będzie nieco łatwiejszy do zniesienia.

Riaz Rizvi
źródło
4
>>> s = bytes("s","utf-8")
>>> print(s)
b's'
>>> s = s.decode("utf-8")
>>> print(s)
s

Cóż, jeśli jest to przydatne w przypadku usuwania irytującej postaci „b”. Jeśli ktoś ma lepszy pomysł, proszę zasugeruj mi lub możesz edytować mnie w dowolnym momencie tutaj. Jestem tylko początkującym

Tapasit Suesasiton
źródło
Można go również używać s.encode('utf-8')tak pythonicznie jak s.decode('utf-8')w zastępstwies = bytes("s", "utf-8")
Hansa Zimermanna
4

Dla Djangow django.test.TestCasetestowanie jednostkowe, zmieniłem python2 składnię:

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

Aby użyć składni 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
źródło