Điểm chính xác của memoryview trong Python là gì


84

Kiểm tra tài liệu trên memoryview:

các đối tượng memoryview cho phép mã Python truy cập dữ liệu bên trong của một đối tượng hỗ trợ giao thức đệm mà không cần sao chép.

class memoryview (obj)

Tạo một chế độ xem bộ nhớ tham chiếu tới đối tượng. obj phải hỗ trợ giao thức đệm. Các đối tượng tích hợp hỗ trợ giao thức đệm bao gồm byte và bytearray.

Sau đó, chúng tôi được cung cấp mã mẫu:

>>> v = memoryview(b'abcefg')
>>> v[1]
98
>>> v[-1]
103
>>> v[1:4]
<memory at 0x7f3ddc9f4350>
>>> bytes(v[1:4])
b'bce'

Báo giá đã qua, bây giờ chúng ta hãy xem xét kỹ hơn:

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

Vì vậy, những gì tôi thu thập được từ những điều trên:

Chúng ta tạo một đối tượng memoryview để hiển thị dữ liệu bên trong của đối tượng đệm mà không cần sao chép, tuy nhiên, để làm bất cứ điều gì hữu ích với đối tượng (bằng cách gọi các phương thức được cung cấp bởi đối tượng), chúng ta phải tạo một bản sao!

Thông thường memoryview (hoặc đối tượng đệm cũ) sẽ cần thiết khi chúng ta có một đối tượng lớn và các lát cắt cũng có thể lớn. Nhu cầu về hiệu quả tốt hơn sẽ xuất hiện nếu chúng ta đang làm các lát lớn, hoặc làm các lát nhỏ nhưng với số lượng lớn.

Với sơ đồ trên, tôi không thấy nó có thể hữu ích như thế nào cho cả hai trường hợp, trừ khi ai đó có thể giải thích cho tôi những gì tôi đang thiếu ở đây.

Chỉnh sửa1:

Chúng tôi có một lượng lớn dữ liệu, chúng tôi muốn xử lý nó bằng cách tiến qua nó từ đầu đến cuối, ví dụ như trích xuất mã thông báo từ đầu chuỗi đệm cho đến khi bộ đệm được sử dụng. Trong thuật ngữ C, điều này là tiến một con trỏ qua bộ đệm và con trỏ có thể được chuyển đến bất kỳ hàm nào mong đợi loại bộ đệm. Làm thế nào một cái gì đó tương tự có thể được thực hiện trong python?

Mọi người đề xuất các giải pháp thay thế, ví dụ như nhiều hàm chuỗi và hàm regex lấy các đối số vị trí có thể được sử dụng để mô phỏng việc tăng tiến một con trỏ. Có hai vấn đề với điều này: đầu tiên đó là một công việc xung quanh, bạn buộc phải thay đổi phong cách mã hóa của mình để khắc phục những thiếu sót và thứ hai: không phải tất cả các hàm đều có đối số vị trí, ví dụ như hàm regex và startswithhãy làm, encode()/ decode()không.

Những người khác có thể đề xuất tải dữ liệu theo từng phần hoặc xử lý bộ đệm trong các phân đoạn nhỏ lớn hơn mã thông báo tối đa. Được rồi, vì vậy chúng tôi đã biết về những cách giải quyết có thể có này, nhưng chúng tôi phải làm việc theo cách tự nhiên hơn trong python mà không cố gắng uốn cong phong cách mã hóa để phù hợp với ngôn ngữ - phải không?

Chỉnh sửa2:

Một mẫu mã sẽ làm cho mọi thứ rõ ràng hơn. Đây là những gì tôi muốn làm và những gì tôi cho rằng chế độ xem bộ nhớ sẽ cho phép tôi làm ngay từ cái nhìn đầu tiên. Hãy sử dụng pmview (chế độ xem bộ nhớ thích hợp) cho chức năng tôi đang tìm kiếm:

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


9
Câu trả lời trong câu hỏi được tham chiếu không cung cấp chi tiết. Câu hỏi cũng không chạm vào các vấn đề tiềm ẩn từ góc độ người học.
Basel Shishani

Câu trả lời:


81

Một lý do memoryviewhữu ích là vì chúng có thể được cắt mà không cần sao chép dữ liệu bên dưới, không giống như bytes/ str.

Ví dụ, lấy ví dụ về đồ chơi sau đây.

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

Trên máy tính của tôi, tôi nhận được

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

Bạn có thể thấy rõ độ phức tạp bậc hai của việc cắt chuỗi lặp lại. Ngay cả khi chỉ với 400000 lần lặp, nó đã không thể thay đổi. Trong khi đó, phiên bản memoryview có độ phức tạp tuyến tính và nhanh như chớp.

Chỉnh sửa: Lưu ý rằng điều này đã được thực hiện trong CPython. Đã xảy ra lỗi trong Pypy phiên bản 4.0.1 khiến các lần xem bộ nhớ có hiệu suất bậc hai.


Ví dụ này không hoạt động trong python 3TypeError: memoryview: a bytes-like object is required, not 'str'
Calculus

@Jose printnhư một câu lệnh cũng không hoạt động trong Python 3. Mã này được viết cho Python 2, mặc dù các thay đổi cần thiết cho python 3 là khá nhỏ.
Antimon

@Yumi Tada, strtrong python3 hoàn toàn khác được định nghĩa trong python2.
hcnhcn012 29/12/17

3
Câu trả lời này không đề cập đến thực tế là để làm gì cả "hữu ích" như Người hỏi đã khẳng định bạn phải sử dụng byte () mà các bản sao các đối tượng ...
ragardner

1
@ citizen2077 Như ví dụ của tôi cho thấy, nó rất hữu ích để thực hiện các thao tác trung gian một cách hiệu quả, ngay cả khi cuối cùng bạn sao chép nó vào một đối tượng byte.
Antimony

58

memoryviewđối tượng là tuyệt vời khi bạn cần các tập con dữ liệu nhị phân chỉ cần hỗ trợ lập chỉ mục. Thay vì phải lấy các lát (và tạo các đối tượng mới, có khả năng lớn) để chuyển đến một API khác, bạn có thể chỉ cần lấy một memoryviewđối tượng.

Một ví dụ API như vậy sẽ là structmô-đun. Thay vì chuyển vào một phần của bytesđối tượng lớn để phân tích cú pháp các giá trị C được đóng gói, bạn chỉ truyền vào một memoryviewvùng mà bạn cần trích xuất các giá trị.

memoryviewcác đối tượng, trên thực tế, hỗ trợ structgiải nén nguyên bản; bạn có thể nhắm mục tiêu một vùng của bytesđối tượng bên dưới bằng một lát cắt, sau đó sử dụng .cast()để 'diễn giải' các byte bên dưới dưới dạng số nguyên dài hoặc giá trị dấu phẩy động hoặc danh sách n-chiều các số nguyên. Điều này giúp cho việc diễn giải định dạng tệp nhị phân rất hiệu quả mà không cần phải tạo thêm bản sao của các byte.


1
Và bạn sẽ làm gì khi cần các tập hợp con hỗ trợ nhiều hơn việc lập chỉ mục ?!
Basel Shishani

2
@BaselShishani: không sử dụng a memoryview. Khi đó, bạn đang xử lý văn bản, không phải dữ liệu nhị phân.
Martijn Pieters

Có, xử lý văn bản. Vì vậy, chúng tôi không sử dụng memoryview, có giải pháp thay thế không?
Basel Shishani

Bạn đang cố gắng giải quyết vấn đề gì? Các chuỗi con bạn cần kiểm tra có lớn không?
Martijn Pieters

6
@BaselShishani: cắt một chế độ xem bộ nhớ trả lại một chế độ xem bộ nhớ mới chỉ bao gồm vùng đó.
Martijn Pieters

4

Hãy để tôi giải thích rõ ràng nơi có trục trặc trong sự hiểu biết ở đây.

Người hỏi, giống như tôi, mong đợi có thể tạo một chế độ xem bộ nhớ để chọn một phần của một mảng hiện có (ví dụ một byte hoặc bytearray). Do đó, chúng tôi mong đợi một cái gì đó như:

desired_slice_view = memoryview(existing_array, start_index, end_index)

Than ôi, không có hàm tạo nào như vậy và thay vào đó các tài liệu không chỉ ra điều gì cần làm.

Điều quan trọng là trước tiên bạn phải tạo một chế độ xem bộ nhớ bao gồm toàn bộ mảng hiện có. Từ chế độ xem bộ nhớ đó, bạn có thể tạo chế độ xem bộ nhớ thứ hai bao gồm một phần của mảng hiện có, như sau:

whole_view = memoryview(existing_array)
desired_slice_view = whole_view[10:20]

Tóm lại, mục đích của dòng đầu tiên chỉ đơn giản là cung cấp một đối tượng mà việc triển khai lát cắt (dunder-getitem) trả về một chế độ xem bộ nhớ.

Điều đó có vẻ rắc rối, nhưng người ta có thể hợp lý hóa nó theo một vài cách:

  1. Đầu ra mong muốn của chúng tôi là một chế độ xem bộ nhớ là một phần của một cái gì đó. Thông thường, chúng ta lấy một đối tượng cắt từ một đối tượng cùng loại bằng cách sử dụng toán tử lát cắt [10:20] trên đó. Vì vậy, có một số lý do để mong đợi rằng chúng ta cần nhận được mong muốn_slice_view của mình từ chế độ xem bộ nhớ, và do đó bước đầu tiên là lấy chế độ xem bộ nhớ của toàn bộ mảng bên dưới.

  2. Việc khai thác một cách ngây thơ của một phương thức khởi tạo memoryview với các đối số bắt đầu và kết thúc không thể xem xét rằng đặc tả lát cắt thực sự cần tất cả tính biểu hiện của toán tử lát cắt thông thường (bao gồm những thứ như [3 :: 2] hoặc [: -4], v.v.). Không có cách nào để chỉ sử dụng toán tử hiện có (và đã hiểu) trong hàm tạo một lớp đó. Bạn không thể đính kèm nó vào đối số current_array, vì điều đó sẽ tạo ra một phần của mảng đó, thay vì cho phương thức khởi tạo memoryview biết một số tham số lát. Và bạn không thể sử dụng chính toán tử làm đối số, vì nó là một toán tử chứ không phải một giá trị hay đối tượng.

Có thể hình dung, một phương thức khởi tạo memoryview có thể nhận một đối tượng lát cắt:

desired_slice_view = memoryview(existing_array, slice(1, 5, 2) )

... nhưng điều đó không khả quan lắm, vì người dùng sẽ phải tìm hiểu về đối tượng lát cắt và các tham số của phương thức khởi tạo của nó có nghĩa là gì, khi họ đã nghĩ về ký hiệu của toán tử lát cắt.


4

Đây là mã 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))

1

Ví dụ xuất sắc của Antimony. Trên thực tế, trong Python3, bạn có thể thay thế dữ liệu = 'x' * n bằng dữ liệu = byte (n) và đặt dấu ngoặc đơn để in các câu lệnh như bên dưới:

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)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.