web-dev-qa-db-fra.com

Quel est exactement le point de memoryview en Python

Vérification de la documentation sur memoryview:

les objets memoryview permettent au code Python d'accéder aux données internes d'un fichier objet qui prend en charge le protocole de tampon sans copier.

classe memoryview (obj)

Créez une memoryview qui fait référence à obj. obj doit supporter le protocole de tampon. Objets intégrés prenant en charge le protocole de tampon inclure les octets et bytearray.

Ensuite, on nous donne l'exemple de code:

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

Citation terminée, regardons maintenant de plus près:

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

Donc ce que je comprends de ce qui précède:

Nous créons un objet memoryview pour exposer les données internes d’un objet tampon sans Copier, mais pour pouvoir faire quoi que ce soit d’utile avec l’objet (en appelant les méthodes Fournies par l’objet), nous devons créer un copie!

En général, memoryview (ou l'ancien objet tampon) est nécessaire lorsque nous avons un objet de grande taille, Et que les tranches peuvent également être volumineuses. La nécessité d'une meilleure efficacité serait présente Si nous faisons de grandes tranches, ou si nous faisons de petites tranches mais un grand nombre de fois.

Avec le schéma ci-dessus, je ne vois pas en quoi cela peut être utile dans les deux cas, à moins que Quelqu'un ne puisse m'expliquer ce que je manque ici.

Edit1:

Nous avons une grande quantité de données, nous voulons les traiter en les parcourant du début à la fin End, par exemple en extrayant des jetons à partir du début d'un tampon de chaîne jusqu'à la consommation du tampon. En terme C, cela fait avancer un pointeur à travers le tampon, et le pointeur peut être passéà n’importe quelle fonction attend le type de tampon. Comment faire quelque chose de similaire en python?

Certains suggèrent des solutions de contournement, par exemple de nombreuses fonctions string et regex prennent des arguments de position qui peuvent être utilisés pour émuler l’avancement d’un pointeur. Il y a deux problèmes avec ceci: premièrement C'est un moyen de contourner le problème, vous êtes obligé de changer votre style de codage pour surmonter les lacunes, et Deuxième: toutes les fonctions n'ont pas d'argument de position, par exemple les fonctions regex et startswith do , encode()decode() not. 

D'autres pourraient suggérer de charger les données en morceaux ou de traiter le tampon dans de petits segments Plus volumineux que le jeton max. Donc, nous sommes conscients de ces solutions de contournement possibles, mais nous sommes supposés travailler de manière plus naturelle en python sans Essayer de modifier le style de codage pour l'adapter à la langue - n'est-ce pas?

_/Edit2:

Un exemple de code rendrait les choses plus claires. C’est ce que je veux faire et ce que j’imaginais que memoryview me permettrait de le faire au premier abord. Permet d’utiliser pmview (vue mémoire appropriée) pour la fonctionnalité que je recherche:

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
48
Basel Shishani

Une des raisons pour lesquelles memoryviews sont utiles est qu’elles peuvent être découpées sans copier les données sous-jacentes, contrairement à bytes/str.

Par exemple, prenons l'exemple de jouet suivant.

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

Sur mon ordinateur, je reçois

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

Vous pouvez clairement voir la complexité quadratique du découpage répété des chaînes. Même avec seulement 400 000 itérations, c'est déjà impossible. Pendant ce temps, la version memoryview a une complexité linéaire et est ultra-rapide.

Edit: Notez que cela a été fait dans CPython. Il y avait un bogue dans Pypy jusqu'à 4.0.1 qui entraînait une performance quadratique de la part de la mémoire.

51
Antimony

Les objets memoryview sont parfaits lorsque vous avez besoin de sous-ensembles de données binaires ne nécessitant que la prise en charge de l'indexation. Au lieu d'avoir à prendre des tranches (et à créer de nouveaux objets potentiellement volumineux) à transmettre à une autre API , vous pouvez simplement prendre un objet memoryview.

Un exemple de cette API serait le module struct. Au lieu de transmettre une tranche du grand objet bytes pour analyser les valeurs C compressées, vous transmettez une memoryview de la région dont vous devez extraire les valeurs.

Les objets memoryview, en fait, prennent en charge struct en décompressant nativement; vous pouvez cibler une région de l'objet bytes sous-jacent avec une tranche, puis utiliser .cast() pour "interpréter" les octets sous-jacents sous forme d'entiers longs, de valeurs à virgule flottante ou de listes d'entiers à n dimensions. Cela rend les interprétations de format de fichier binaire très efficaces, sans avoir à créer plus de copies des octets.

36
Martijn Pieters

Voici le code python3. 

#!/usr/bin/env python3

import time
for n in (100000, 200000, 300000, 400000):
    data = '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))
2
jimaf

Excellent exemple de Antimony . En fait, en Python3, vous pouvez remplacer data = 'x' * n par data = bytes (n) et mettre des parenthèses pour imprimer des instructions comme suit: 

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)
0
user2494386

Laissez-moi préciser où réside le problème dans la compréhension ici.

Le questionneur, comme moi, s'attendait à pouvoir créer une memoryview qui sélectionne une tranche d'un tableau existant (par exemple, un octet ou un bytearray). Nous nous attendions donc à quelque chose comme:

desired_slice_view = memoryview(existing_array, start_index, end_index)

Hélas, il n’existe pas de constructeur de ce type et les documents ne précisent pas quoi faire.

La clé est que vous devez d’abord créer une mémoire qui couvre l’ensemble du tableau existant. À partir de cette mémoire, vous pouvez créer une deuxième mémoire qui couvre une tranche du tableau existant, comme ceci:

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

En bref, le but de la première ligne est simplement de fournir un objet dont l'implémentation de tranche (dunder-getitem) renvoie une mémoire.

Cela peut sembler désordonné, mais on peut le rationaliser de deux manières:

  1. Notre sortie souhaitée est une memoryview qui est une tranche de quelque chose. Normalement, nous obtenons un objet découpé à partir d'un objet du même type, en utilisant l'opérateur de découpage [10:20]. Il y a donc des raisons de penser que nous devons extraire notre désir_slice_view à partir d'une mémoire, et que la première étape consiste donc à obtenir une mémoire de l'ensemble du tableau sous-jacent.

  2. L'exposition naïve d'un constructeur memoryview avec les arguments start et end ne permet pas de considérer que la spécification de tranche a réellement besoin de toute l'expressivité de l'opérateur de tranche habituel (y compris des éléments comme [3 :: 2] ou [: -4], etc.). Il n'y a aucun moyen d'utiliser simplement l'opérateur existant (et compris) dans ce constructeur one-liner. Vous ne pouvez pas l'attacher à l'argument existing_array, car cela ferait une tranche de ce tableau, au lieu d'indiquer quelques paramètres de tranche au constructeur memoryview. Et vous ne pouvez pas utiliser l'opérateur lui-même comme argument, car il s'agit d'un opérateur et non d'une valeur ou d'un objet.

En théorie, un constructeur memoryview pourrait prendre un objet slice:

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

... mais ce n'est pas très satisfaisant, car les utilisateurs devraient en apprendre davantage sur l'objet slice et sur la signification des paramètres de son constructeur, alors qu'ils pensent déjà en termes de notation de l'opérateur slice.

0
gwideman