Bienvenue sur IndexError.

Ici vous pouvez poser des questions sur Python et le Framework Django.

Mais aussi sur les technos front comme React, Angular, Typescript et Javascript en général.

Consultez la FAQ pour améliorer vos chances d'avoir des réponses à vos questions.

RingList, un nouveau type de liste qui boucle sur lui même...

+1 vote

Je voudrai une liste "alternative" qui ait ce comportement:

caroussel_diapo = ringlist()
caroussel_diapo.append("juju_au_ski.jpeg","juju_a_la_mer.jpeg","juju_en_forme.jpeg"]

Lorsque je fais

x = caroussel_diapo[3]

plutot qu' obtenir un IndexError: list index out of range, je voudrais revenir sur l' index 0.

Question bonus : admettons que je veuille empêcher une lecture retro-grade, telle que

caroussel_diapo[-1]

reste bloqué sur l' index 0 plutôt que le "dernier" élément?

Et pour brider la fonction append à 255 diapo maximum ?

Et rendre non modifiable la diapo zéro (exemple : y afficher une pub ou un logo pour une marque)

Pour le faire par héritage du type list , faut il mettre des try/except sur un getter ?
Y a t il un intérêt a le faire avec une méta classe ?
enter image description here

lien intéressant:
http://sametmax.com/le-guide-ultime-et-definitif-sur-la-programmation-orientee-objet-en-python-a-lusage-des-debutants-qui-sont-rassures-par-les-textes-detailles-qui-prennent-le-temps-de-tout-expliquer-partie-8/

demandé 24-Jan par buffalo974 (2,608 points)
edité 25-Jan par buffalo974

3 Réponses

+5 votes
 
Meilleure réponse

Une première idée c'est de créer une classe qui hérite de list pour de changer le comportement de récupération des éléments: la méthode getitem utilisée lorsqu'on appelle un élément avec la syntaxe : self[indexorkey]

Voici ce que ça donne :

class ring_list(list):
    def __getitem__(self, y):
        """x.__getitem__(y) <==> x[y]"""
        return super().__getitem__(y % len(self))

On modifie ainsi le comportement de getitem face à l'argument d'index :

y % len(self)

permet de toujours retourner un nombre compris dans la gamme d'index de 0 à la longueur de la liste.

Exemple :

set([(i % 3) for i in range(30)])

retournera toujours 0, 1, 2

Il ne reste plus qu'à appeler la méthode d'origine pour récupérer et retourner l'élément
super().getitem est la méthode python 3 uniquement (il me semble)

sinon la syntaxe compatible 2 & 3 :
list.getitem(self, y % len(self))

(Je sais que super existe en python 2 mais je ne sais pas l'utiliser)

Il y a plus qu'à jouer :

caroussel_diapo = ring_list()
caroussel_diapo.extend(["juju_au_ski.jpeg","juju_a_la_mer.jpeg","juju_en_forme.jpeg"])

diaporama = list()
for i in range(30):
    diaporama.append(caroussel_diapo[i])

# Vérification de l'égalité de tirage: Exemple avec 30: [10, 10, 10]
print([diaporama.count(v) for v in caroussel_diapo])
répondu 26-Jan par YCL1 (166 points)
sélectionné 28-Jan par buffalo974

oui je vais m'en servir !

Attention, tu as cassé ici le comportement courant des listes, vu que le slicing ne fonctionne plus (par exemple)...

@yoch On peut essayer de gérer le slice (exemple: stackoverflow.com/a/3912107/4963472) mais vu qu'il faut permettre d'extrapoler le tableau pour gérer le même comportement que par index c'est un peu plus compliqué à faire.

Méthode sans extrapolation :

class ring_list(list):
    def __getitem__(self, y, *args, **kwargs):
        """x.__getitem__(y) <==> x[y]"""
        if isinstance(y, slice):
            return super().__getitem__(y)
        else:
            return super().__getitem__(y % len(self))

Avec extrapolation du tableau, au passage je viens de découvrir que le comprehension list ne fonctionne pas avec super :

class ring_list(list):
    def __getitem__(self, y, *args, **kwargs):
        """x.__getitem__(y) <==> x[y]"""
        if isinstance(y, slice):
            start, stop, step = y.indices( # len: absolute from start(None: 0) to stop(None: default length)
                abs((y.start or 0) - [(y.stop or 0), len(self)][y.stop is None])
            )
            data_slice = list()
            for i in range(start, stop, step):
                data_slice.append(super().__getitem__(i % len(self)))
            return data_slice
        else:
            return super().__getitem__(y % len(self))

Ainsi avec caroussel_diapo[:30:] on retrouve le même comportement qu'indexer depuis un range(30) :

caroussel_diapo = ring_list()
caroussel_diapo.extend(["juju_au_ski.jpeg","juju_a_la_mer.jpeg","juju_en_forme.jpeg"])

assert [caroussel_diapo[:30:].count(v) for v in caroussel_diapo] == [10, 10, 10]
assert [caroussel_diapo[:30:3].count(v) for v in caroussel_diapo] == [10, 0, 0]
assert [caroussel_diapo[1:31:3].count(v) for v in caroussel_diapo] == [0, 10, 0]
assert [caroussel_diapo[2:32:3].count(v) for v in caroussel_diapo] == [0, 0, 10]
assert caroussel_diapo[::-1] == list(caroussel_diapo)[::-1]

comment je peux refaire le code de YCL1 sans l'operateur % ?

+2 votes

Tout d'abord, attention à ne pas mettre des comportements trop exotiques sur les syntaxes de base, il arrive qu'on n'y comprenne plus rien et qu'on le regrette.

Dans ton cas, cela veut dire: veux-tu absolument changer le comportement de truc[i] ?
On peut éviter les confusions en utilisant une méthode dédiée : truc.get_cycle(i) par exemple.

Une implémentation possible serait:

class RingList(list):
    def get_cycle(self, idx):
        idx = max(0, idx)
        return self[idx % len(self)]

Pour les questions qui visent à interdire certaines actions spécifiques (indices 0 bloqué, taille max à 255), pose toi bien la question suivante : que doit-il se passer quand on enfreint la règle ? action ignorée, erreur, warning, ... ?

Pour ta dernière question, je ne vois pas de raison d'utiliser les métaclasses dans ce que tu veux faire.

répondu 26-Jan par bubulle (2,106 points)
+5 votes

Je pense que ta "liste" est suffisamment différente d'un objet list pour affirmer qu’hériter de list est une fausse bonne idée. Ici, tu devrais plutôt définir un nouveau type de conteneur qui respecte tes propres spécifications.

Par ailleurs, juste pour info (vu que ça ne répond pas à ton besoin), il existe ceci.

répondu 28-Jan par yoch (2,294 points)

bien vu.
Je pense qu'il faut que j' explore les deux façons et que je voie à l'usage
le moins casse-tête.

...