38 votes

Pourquoi la liste pose-t-elle des questions sur __len__?

class Foo:
    def __getitem__(self, item):
        print('getitem', item)
        if item == 6:
            raise IndexError
        return item**2
    def __len__(self):
        print('len')
        return 3

class Bar:
    def __iter__(self):
        print('iter')
        return iter([3, 5, 42, 69])
    def __len__(self):
        print('len')
        return 3

Démo:

>>> list(Foo())
len
getitem 0
getitem 1
getitem 2
getitem 3
getitem 4
getitem 5
getitem 6
[0, 1, 4, 9, 16, 25]
>>> list(Bar())
iter
len
[3, 5, 42, 69]

Pourquoi est - list appel __len__? Il ne semble pas utiliser le résultat pour rien d'évident. Un for boucle de ne pas le faire. Ce n'est pas mentionné nulle part dans l' itérateur protocole, qui parle de __iter__ et __next__.

Est-ce Python réservation d'espace pour la liste à l'avance, ou quelque chose d'intelligent comme ça?

(Disponible 3.6.0 sur Linux)

42voto

Jim Points 8793

Voir la section "Justification" de PEP 424 , qui a introduit __length_hint__ et propose un éclairage sur la motivation:

Être en mesure de pré-allouer des listes, en fonction de la taille attendue, estimé par __length_hint__ , peut être une optimisation importante. Disponible a été observé pour exécuter du code plus rapide que PyPy, simplement parce que de cette optimisation être présent.

En outre, la documentation pour object.__length_hint__ vérifie le fait que c'est purement une fonction d'optimisation:

Appelés à mettre en oeuvre operator.length_hint(). Doit retourner une durée estimée de l'objet (qui peut être supérieure ou inférieure à la longueur réelle). La longueur doit être un entier >= 0. Cette méthode est purement une optimisation et n'est jamais requise pour l'exactitude.

Donc, __length_hint__ est ici car elle peut aboutir à quelques belles optimisations.

PyObject_LengthHint, essaie d'abord d'obtenir une valeur de object.__len__ (si elle est définie) et puis essaye de voir si object.__length_hint__ est disponible. Si il n'y a, elle renvoie une valeur par défaut de 8 pour les listes.

listextend, qui est appelée à partir d' list_init que Eli a déclaré, dans sa réponse, a été modifié en fonction de ce PEPS à offrir cette optimisation pour tout ce qui définit soit une __len__ ou __length_hint__.

list n'est pas le seul qui bénéficie de cette situation, bien sûr, bytes d'objets:

>>> bytes(Foo())
len
getitem 0
...
b'\x00\x01\x04\t\x10\x19'

donc, dois - bytearray objets, mais seulement lorsque vous extend leur:

>>> bytearray().extend(Foo())
len
getitem 0
...

et tuple objets qui créent un intermédiaire de la séquence de remplir eux-mêmes:

>>> tuple(Foo())
len
getitem 0
...
(0, 1, 4, 9, 16, 25)

Si quelqu'un se promène exactement pourquoi 'iter' est imprimé avant d' 'len' dans la classe Bar , et non après, comme il arrive avec la classe Foo:

C'est parce que si l'objet en main définit un __iter__ Python sera la première à l'appeler pour obtenir de l'itérateur, permettant ainsi l'exécution de l' print('iter') trop. La même chose ne se produise pas, si elle tombe en arrière à l'aide de __getitem__.

32voto

Eli Bendersky Points 82298

list est un objet de la liste d'constructeur qui va allouer une première tranche de la mémoire de son contenu. La liste constructeur tente de trouver une bonne taille pour cette première tranche de la mémoire par la vérification de la longueur de l'indice ou de la longueur d'un objet passé dans le constructeur . Voir l'appel à PyObject_LengthHint dans le Python source ici. Cet endroit est appelé à partir de la liste de constructeur, list_init

Si votre objet n'a pas de __len__ ou __length_hint__, c'est OK -- d'une valeur par défaut de 8 est utilisé; il s'agit peut-être de moins en moins efficace en raison de réaffectations.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X