Est-il possible d'implémenter une boucle Python for range sans variable d'itérateur?


187

Est-il possible de suivre sans le i?

for i in range(some_number):
    # do something

Si vous voulez juste faire quelque chose N fois et n'avez pas besoin de l'itérateur.


21
C'est une bonne question! PyDev marque même le «i» comme un avertissement pour «variable non utilisée». La solution ci-dessous supprime cet avertissement.
Ashwin Nanjappa

@Ashwin Vous pouvez utiliser \ @UnusedVariable pour supprimer cet avertissement. Notez que j'avais besoin d'échapper au symbole «at» pour faire passer ce commentaire.
Raffi Khatchadourian

Je dirige la même question que vous. C'est ennuyeux avec les avertissements de pylint. Bien sûr, vous pouvez désactiver les avertissements par suppression supplémentaire comme @Raffi Khatchadourian proposé. Ce serait bien d'éviter les avertissements de pylint et les commentaires de suppression.
tangoal le

Réponses:


110

Du haut de ma tête, non.

Je pense que le mieux que vous puissiez faire est quelque chose comme ceci:

def loop(f,n):
    for i in xrange(n): f()

loop(lambda: <insert expression here>, 5)

Mais je pense que vous pouvez simplement vivre avec la ivariable supplémentaire .

Voici la possibilité d'utiliser la _variable, qui en réalité, n'est qu'une autre variable.

for _ in range(n):
    do_something()

Notez que _le dernier résultat renvoyé dans une session Python interactive est attribué:

>>> 1+2
3
>>> _
3

Pour cette raison, je ne l'utiliserais pas de cette manière. Je ne connais aucun idiome mentionné par Ryan. Cela peut gâcher votre interprète.

>>> for _ in xrange(10): pass
...
>>> _
9
>>> 1+2
3
>>> _
9

Et selon la grammaire Python , c'est un nom de variable acceptable:

identifier ::= (letter|"_") (letter | digit | "_")*

4
"Mais je pense que vous pouvez simplement vivre avec le" i "supplémentaire" Ouais c'est juste un point académique.
James McMahon

1
@nemo, vous pouvez essayer de faire pour _ in range (n): si vous ne voulez pas utiliser de noms alphanumériques.
Inconnu

Est-ce que _ est une variable dans ce cas? Ou est-ce quelque chose d'autre en Python?
James McMahon

1
@nemo Oui, c'est juste un nom de variable acceptable. Dans l'interpréteur, la dernière expression que vous avez créée lui est automatiquement attribuée.
Inconnu

3
@kurczak Il y a un point. L'utilisation _indique clairement qu'il doit être ignoré. Dire que cela ne sert à rien, c'est comme dire qu'il est inutile de commenter votre code - parce que cela ferait exactement la même chose de toute façon.
Lambda Fairy

69

Vous cherchez peut-être

for _ in itertools.repeat(None, times): ...

c'est LE moyen le plus rapide d'itérer les timestemps en Python.


2
Je n'étais pas préoccupé par la performance, j'étais simplement curieux de savoir s'il y avait une manière plus courte d'écrire la déclaration. Bien que j'utilise Python de manière sporadique depuis environ 2 ans maintenant, j'ai toujours l'impression qu'il me manque beaucoup de choses. Itertools est l'une de ces choses, merci pour l'information.
James McMahon

5
C'est intéressant, je n'en étais pas conscient. Je viens de jeter un œil à la documentation itertools; mais je me demande pourquoi est-ce plus rapide que d'utiliser simplement range ou xrange?
si28719e

5
@blackkettle: c'est plus rapide car il n'a pas besoin de renvoyer l'index d'itération actuel, qui est une partie mesurable du coût de xrange (et de la plage de Python 3, qui donne un itérateur, pas une liste). @nemo, range est aussi optimisé que possible, mais avoir besoin de construire et de retourner une liste est inévitablement un travail plus lourd qu'un itérateur (dans Py3, range renvoie un itérateur, comme le xrange de Py2; la rétrocompatibilité ne permet pas un tel changement dans Py2), en particulier celui qui n'a pas besoin de renvoyer une valeur variable.
Alex Martelli

4
@Cristian, oui, préparer et renvoyer clairement un int Python à chaque fois, inc. gc fonctionne, a un coût mesurable - l'utilisation d'un compteur en interne n'a pas d'importance.
Alex Martelli le

4
Je comprends maintenant. La différence vient de la surcharge du GC, pas de l '«algorithme». Au fait, je lance un benchmark timeit rapide et l'accélération était d'environ 1,42x.
Cristian Ciupitu

59

L'idiome général pour attribuer à une valeur qui n'est pas utilisée est de la nommer _.

for _ in range(times):
    do_stuff()

18

Ce que tout le monde vous suggère d'utiliser _ ne dit pas, c'est que _ est fréquemment utilisé comme raccourci vers l'une des fonctions de gettext , donc si vous voulez que votre logiciel soit disponible dans plus d'une langue, il vaut mieux éviter de l'utiliser à d'autres fins.

import gettext
gettext.bindtextdomain('myapplication', '/path/to/my/language/directory')
gettext.textdomain('myapplication')
_ = gettext.gettext
# ...
print _('This is a translatable string.')

Pour moi, cette utilisation de _semble être une idée terrible, cela ne me dérangerait pas d'être en conflit avec elle.
KeithWM

9

Voici une idée aléatoire qui utilise (abuse?) Le modèle de données ( lien Py3 ).

class Counter(object):
    def __init__(self, val):
        self.val = val

    def __nonzero__(self):
        self.val -= 1
        return self.val >= 0
    __bool__ = __nonzero__  # Alias to Py3 name to make code work unchanged on Py2 and Py3

x = Counter(5)
while x:
    # Do something
    pass

Je me demande s'il y a quelque chose comme ça dans les bibliothèques standard?


10
Je pense qu'avoir une méthode telle que celle __nonzero__des effets secondaires est une idée horrible.
ThiefMaster

2
J'utiliserais à la __call__place. while x():n'est pas si difficile à écrire.
Jasmijn

1
Il existe également un argument pour éviter le nom Counter; bien sûr, ce n'est pas réservé ou dans la portée intégrée, mais collections.Counterc'est une chose , et créer une classe du même nom risque de confondre le responsable (non pas que cela ne risque pas déjà).
ShadowRanger

7

Vous pouvez utiliser _11 (ou n'importe quel nombre ou un autre identifiant non valide) pour empêcher la colision de nom avec gettext. Chaque fois que vous utilisez un trait de soulignement + un identificateur non valide, vous obtenez un nom factice qui peut être utilisé dans la boucle for.


Agréable! PyDev est d'accord avec vous: cela supprime l'avertissement jaune "Variable inutilisée".
mike rodent

2

Peut-être que la réponse dépendra du problème que vous rencontrez avec l'utilisation de l'itérateur? peut être utilisé

i = 100
while i:
    print i
    i-=1

ou

def loop(N, doSomething):
    if not N:
        return
    print doSomething(N)
    loop(N-1, doSomething)

loop(100, lambda a:a)

mais franchement, je ne vois aucun intérêt à utiliser de telles approches


1
Remarque: Python (certainement pas l'interpréteur de référence CPython du moins, probablement pas la plupart des autres) n'optimise pas la récursivité de la queue, donc N sera limité à quelque chose dans le voisinage de la valeur de sys.getrecursionlimit()(qui par défaut à quelque part dans les quatre plage de chiffres sur CPython); utiliser sys.setrecursionlimitaugmenterait la limite, mais finalement vous atteindriez la limite de pile C et l'interpréteur mourrait avec un débordement de pile (pas seulement en augmentant un gentil RuntimeError/ RecursionError).
ShadowRanger


1

Au lieu d'un compteur inutile, vous avez maintenant une liste inutile. La meilleure solution consiste à utiliser une variable commençant par «_», qui indique aux vérificateurs de syntaxe que vous savez que vous n'utilisez pas la variable.

x = range(5)
while x:
  x.pop()
  print "Work!"

0

Je suis généralement d'accord avec les solutions données ci-dessus. A savoir avec:

  1. Utilisation du trait de soulignement dans for-loop (2 lignes et plus)
  2. Définition d'un whilecompteur normal (3 lignes et plus)
  3. Déclaration d'une classe personnalisée avec __nonzero__implémentation (beaucoup plus de lignes)

Si l'on doit définir un objet comme au n ° 3, je recommanderais d'implémenter le protocole avec un mot - clé ou d'appliquer contextlib .

De plus, je propose encore une autre solution. Il s'agit d'un 3 lignes et n'est pas d'une élégance suprême, mais il utilise le package itertools et pourrait donc être d'un intérêt.

from itertools import (chain, repeat)

times = chain(repeat(True, 2), repeat(False))
while next(times):
    print 'do stuff!'

Dans cet exemple, 2 est le nombre d'itérations de la boucle. chain encapsule deux itérateurs répétés , le premier étant limité mais le second est infini. Rappelez-vous que ce sont de véritables objets itérateurs, donc ils ne nécessitent pas de mémoire infinie. Évidemment, c'est beaucoup plus lent que la solution n ° 1 . À moins d'être écrit dans le cadre d'une fonction, il peut nécessiter un nettoyage de la variable times .


2
chainest inutile, times = repeat(True, 2); while next(times, False):fait la même chose.
AChampion

0

Nous nous sommes amusés avec ce qui suit, intéressant à partager donc:

class RepeatFunction:
    def __init__(self,n=1): self.n = n
    def __call__(self,Func):
        for i in xrange(self.n):
            Func()
        return Func


#----usage
k = 0

@RepeatFunction(7)                       #decorator for repeating function
def Job():
    global k
    print k
    k += 1

print '---------'
Job()

Résultats:

0
1
2
3
4
5
6
---------
7

0

Si do_somethingest une fonction simple ou peut être enveloppée dans une seule, une simple map()boîte peut do_something range(some_number)fois:

# Py2 version - map is eager, so it can be used alone
map(do_something, xrange(some_number))

# Py3 version - map is lazy, so it must be consumed to do the work at all;
# wrapping in list() would be equivalent to Py2, but if you don't use the return
# value, it's wastefully creating a temporary, possibly huge, list of junk.
# collections.deque with maxlen 0 can efficiently run a generator to exhaustion without
# storing any of the results; the itertools consume recipe uses it for that purpose.
from collections import deque

deque(map(do_something, range(some_number)), 0)

Si vous souhaitez passer des arguments à do_something, vous pouvez également trouver que la recette itertools serepeatfunc lit bien:

Pour passer les mêmes arguments:

from collections import deque
from itertools import repeat, starmap

args = (..., my args here, ...)

# Same as Py3 map above, you must consume starmap (it's a lazy generator, even on Py2)
deque(starmap(do_something, repeat(args, some_number)), 0)

Pour passer différents arguments:

argses = [(1, 2), (3, 4), ...]

deque(starmap(do_something, argses), 0)

-1

Si vous voulez vraiment éviter de mettre quelque chose avec un nom (soit une variable d'itération comme dans l'OP, soit une liste indésirable ou un générateur indésirable renvoyant vrai le temps voulu), vous pouvez le faire si vous le souhaitez vraiment:

for type('', (), {}).x in range(somenumber):
    dosomething()

L'astuce utilisée est de créer une classe anonyme type('', (), {})qui aboutit à une classe avec un nom vide, mais NB qu'elle n'est pas insérée dans l'espace de noms local ou global (même si un nom non vide a été fourni). Ensuite, vous utilisez un membre de cette classe comme variable d'itération qui est inaccessible car la classe dont il est membre est inaccessible.


De toute évidence, c'est intentionnellement pathologique, donc critiquer est hors de propos, mais je noterai ici un écueil supplémentaire. Sur CPython, l'interpréteur de référence, les définitions de classe sont naturellement cycliques (la création d'une classe crée inévitablement un cycle de référence qui empêche le nettoyage déterministe de la classe basé sur le comptage de références). Cela signifie que vous attendez le GC cyclique pour entrer et nettoyer la classe. Il sera généralement collecté dans le cadre de la jeune génération, qui par défaut est collectée fréquemment, mais même ainsi, chaque boucle signifie ~ 1,5 Ko de déchets avec une durée de vie non déterministe.
ShadowRanger

Fondamentalement, pour éviter une variable nommée qui serait (généralement) nettoyée de manière déterministe sur chaque boucle (lorsqu'elle rebondit et que l'ancienne valeur est nettoyée), vous créez une énorme variable sans nom qui est nettoyée de manière non déterministe et qui pourrait facilement dure plus longtemps.
ShadowRanger


-7

Qu'en est-il de:

while range(some_number):
    #do something

3
C'est une boucle infinie car la condition range(some_number)est toujours vraie!
mortel

@deadly: Eh bien, si some_numberest inférieur ou égal à 0, ce n'est pas infini, il ne s'exécute jamais. :-) Et c'est plutôt inefficace pour une boucle infinie (surtout sur Py2), car cela crée un list(Py2) ou un rangeobjet (Py3) frais pour chaque test (ce n'est pas une constante du point de vue de l'interpréteur, il faut charger rangeet some_numberchaque boucle, appelez range, puis testez le résultat).
ShadowRanger
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.