Sprawdzanie dokumentacji dotyczącej podglądu pamięci:
obiekty memoryview umożliwiają kodowi Python dostęp do wewnętrznych danych obiektu, który obsługuje protokół bufora, bez kopiowania.
klasa memoryview (obj)
Utwórz widok pamięci, który odwołuje się do obj. obj musi obsługiwać protokół bufora. Wbudowane obiekty, które obsługują protokół bufora, obejmują bajty i bajt.
Następnie otrzymujemy przykładowy kod:
>>> v = memoryview(b'abcefg')
>>> v[1]
98
>>> v[-1]
103
>>> v[1:4]
<memory at 0x7f3ddc9f4350>
>>> bytes(v[1:4])
b'bce'
Cytat zakończony, teraz przyjrzyjmy się bliżej:
>>> b = b'long bytes stream'
>>> b.startswith(b'long')
True
>>> v = memoryview(b)
>>> vsub = v[5:]
>>> vsub.startswith(b'bytes')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'memoryview' object has no attribute 'startswith'
>>> bytes(vsub).startswith(b'bytes')
True
>>>
Więc co z powyższego wyciągam:
Tworzymy obiekt memoryview, aby ujawnić wewnętrzne dane obiektu buforowego bez kopiowania, jednak aby zrobić cokolwiek pożytecznego z obiektem (przez wywołanie metod dostarczonych przez obiekt), musimy utworzyć kopię!
Zwykle memoryview (lub stary obiekt bufora) byłby potrzebny, gdy mamy duży obiekt, a plasterki również mogą być duże. Potrzeba lepszej wydajności byłaby obecna, gdybyśmy robili duże plasterki lub robili małe plasterki, ale dużą liczbę razy.
Z powyższym schematem nie widzę, jak może być przydatny w obu sytuacjach, chyba że ktoś może mi wyjaśnić, czego tutaj brakuje.
Edycja1:
Mamy dużą porcję danych, chcemy ją przetworzyć, przechodząc przez nią od początku do końca, na przykład wyodrębniając tokeny od początku bufora ciągów do momentu zużycia bufora. W języku C oznacza to przesunięcie wskaźnika przez bufor, a wskaźnik można przekazać do dowolnej funkcji, która oczekuje typu buforu. Jak można zrobić coś podobnego w Pythonie?
Ludzie sugerują obejście tego problemu, na przykład wiele funkcji ciągów i wyrażeń regularnych przyjmuje argumenty pozycji, które mogą być używane do emulacji przesuwania wskaźnika. Są z tym dwa problemy: po pierwsze jest to obejście, jesteś zmuszony zmienić styl kodowania, aby przezwyciężyć niedociągnięcia, a po drugie: nie wszystkie funkcje mają argumenty pozycji, na przykład funkcje regex i startswith
wykonaj, encode()
/ decode()
nie.
Inni mogą sugerować ładowanie danych porcjami lub przetwarzanie buforu w małych segmentach większych niż maksymalny token. OK, więc jesteśmy świadomi tych możliwych obejść, ale powinniśmy pracować w Pythonie w bardziej naturalny sposób, bez próby naginania stylu kodowania, aby pasował do języka - czyż nie?
Edit2:
Przykładowy kod sprawiłby, że wszystko było jaśniejsze. To jest to, co chcę zrobić i na pierwszy rzut oka założyłem, że widok wspomnień pozwoli mi zrobić. Użyjmy pmview (właściwego widoku pamięci) dla funkcji, której szukam:
tokens = []
xlarge_str = get_string()
xlarge_str_view = pmview(xlarge_str)
while True:
token = get_token(xlarge_str_view)
if token:
xlarge_str_view = xlarge_str_view.vslice(len(token))
# vslice: view slice: default stop paramter at end of buffer
tokens.append(token)
else:
break
źródło
Odpowiedzi:
Jednym z powodów,
memoryview
dla których są przydatne, jest to, że można je kroić bez kopiowania danych źródłowych, w przeciwieństwie dobytes
/str
.Na przykład weźmy następujący przykład zabawki.
import time for n in (100000, 200000, 300000, 400000): data = 'x'*n start = time.time() b = data while b: b = b[1:] print 'bytes', n, time.time()-start for n in (100000, 200000, 300000, 400000): data = 'x'*n start = time.time() b = memoryview(data) while b: b = b[1:] print 'memoryview', n, time.time()-start
Na moim komputerze otrzymuję
bytes 100000 0.200068950653 bytes 200000 0.938908100128 bytes 300000 2.30898690224 bytes 400000 4.27718806267 memoryview 100000 0.0100269317627 memoryview 200000 0.0208270549774 memoryview 300000 0.0303030014038 memoryview 400000 0.0403470993042
Wyraźnie widać kwadratową złożoność powtarzanego cięcia struny. Nawet przy zaledwie 400000 iteracjach jest już niezmienialny. Tymczasem wersja pamięci ma liniową złożoność i jest błyskawiczna.
Edycja: Zauważ, że zostało to zrobione w CPythonie. Wystąpił błąd w Pypy do wersji 4.0.1, który powodował, że podglądy pamięci miały wydajność kwadratową.
źródło
TypeError: memoryview: a bytes-like object is required, not 'str'
print
jako instrukcja również nie działa w Pythonie 3. Ten kod został napisany dla Pythona 2, chociaż zmiany wymagane w Pythonie 3 są dość trywialne.str
w python3 jest zupełnie inaczej zdefiniowane w python2.memoryview
obiekty są świetne, gdy potrzebujesz podzbiorów danych binarnych, które muszą obsługiwać tylko indeksowanie. Zamiast pobierać plasterki (i tworzyć nowe, potencjalnie duże) obiekty, aby przekazać je do innego interfejsu API , możesz po prostu wziąćmemoryview
obiekt.Jednym z takich przykładów API byłby
struct
moduł. Zamiast przekazywać wycinek dużegobytes
obiektu w celu przeanalizowania upakowanych wartości C, należy przekazaćmemoryview
tylko obszar, z którego należy wyodrębnić wartości.memoryview
obiekty w rzeczywistości obsługująstruct
rozpakowywanie natywnie; możesz wycelować w regionbytes
obiektu bazowego za pomocą wycinka, a następnie użyć go.cast()
do „interpretacji” bazowych bajtów jako długie liczby całkowite, wartości zmiennoprzecinkowe lub n-wymiarowe listy liczb całkowitych. Zapewnia to bardzo wydajną interpretację formatu plików binarnych, bez konieczności tworzenia większej liczby kopii bajtów.źródło
memoryview
. Masz do czynienia z tekstem, a nie z danymi binarnymi.Pozwólcie, że wyjaśnię, na czym polega błąd w zrozumieniu.
Pytający, podobnie jak ja, oczekiwał, że będzie w stanie stworzyć widok pamięci, który wybierze wycinek istniejącej tablicy (na przykład bajty lub bajt). Dlatego spodziewaliśmy się czegoś takiego:
desired_slice_view = memoryview(existing_array, start_index, end_index)
Niestety, nie ma takiego konstruktora, a dokumentacja nie określa, co należy zrobić.
Kluczem jest to, że musisz najpierw zrobić podgląd pamięci, który obejmuje całą istniejącą tablicę. Z tego widoku pamięci można utworzyć drugi podgląd pamięci, który obejmuje wycinek istniejącej tablicy, na przykład:
whole_view = memoryview(existing_array) desired_slice_view = whole_view[10:20]
Krótko mówiąc, celem pierwszej linii jest po prostu dostarczenie obiektu, którego implementacja plastra (dunder-getitem) zwraca widok pamięci.
To może wydawać się nieporządne, ale można to zracjonalizować na kilka sposobów:
Naszym pożądanym wynikiem jest widok pamięci, który jest kawałkiem czegoś. Zwykle otrzymujemy obiekt pokrojony w plasterki z obiektu tego samego typu, używając na nim operatora wycinka [10:20]. Jest więc jakiś powód, by oczekiwać, że musimy uzyskać nasz pożądany_widok_slice z widoku pamięci i dlatego pierwszym krokiem jest uzyskanie obrazu pamięci całej podstawowej tablicy.
Naiwne oczekiwanie konstruktora Memoryview z argumentami początkowymi i końcowymi nie uwzględnia faktu, że specyfikacja wycinka naprawdę potrzebuje całej ekspresji zwykłego operatora wycinka (włączając takie rzeczy jak [3 :: 2] lub [: -4] itd.). Nie ma sposobu, aby po prostu użyć istniejącego (i zrozumiałego) operatora w tym konstruktorze jednowierszowym. Nie możesz dołączyć go do argumentu existing_array, ponieważ spowoduje to utworzenie wycinka tej tablicy, zamiast przekazywania konstruktorowi memoryview niektórych parametrów wycinka. I nie możesz użyć samego operatora jako argumentu, ponieważ jest to operator, a nie wartość lub obiekt.
Można sobie wyobrazić, że konstruktor Memoryview mógłby wziąć obiekt wycinka:
desired_slice_view = memoryview(existing_array, slice(1, 5, 2) )
... ale to nie jest zbyt satysfakcjonujące, ponieważ użytkownicy musieliby dowiedzieć się o obiekcie plasterka i co oznaczają jego parametry konstruktora, kiedy już myślą w kategoriach notacji operatora wycinka.
źródło
Oto kod python3.
#!/usr/bin/env python3 import time for n in (100000, 200000, 300000, 400000): data = b'x'*n start = time.time() b = data while b: b = b[1:] print ('bytes {:d} {:f}'.format(n,time.time()-start)) for n in (100000, 200000, 300000, 400000): data = b'x'*n start = time.time() b = memoryview(data) while b: b = b[1:] print ('memview {:d} {:f}'.format(n,time.time()-start))
źródło
Doskonały przykład autorstwa Antymona. Właściwie w Pythonie3 możesz zamienić dane = 'x' * n na dane = bajty (n) i umieścić nawiasy na wypisanych instrukcjach, jak poniżej:
import time for n in (100000, 200000, 300000, 400000): #data = 'x'*n data = bytes(n) start = time.time() b = data while b: b = b[1:] print('bytes', n, time.time()-start) for n in (100000, 200000, 300000, 400000): #data = 'x'*n data = bytes(n) start = time.time() b = memoryview(data) while b: b = b[1:] print('memoryview', n, time.time()-start)
źródło