Question

J'ai une fonction dont l'argument d'entrée peut être un élément ou une liste d'éléments. Si cet argument est un élément unique, je le mets dans une liste afin de pouvoir parcourir les entrées de manière cohérente.

Actuellement, j'ai ceci:

def my_func(input):
    if not isinstance(input, list): input = [input]
    for e in input:
        ...

Je travaille avec une API existante, je ne peux donc pas modifier les paramètres d'entrée. Utiliser isinstance () donne le sentiment d’être hacky, existe-t-il un moyen approprié de procéder?

Était-ce utile?

La solution

J'aime la suggestion de Andrei Vajna de hasattr (var, '__ iter __') . Notez ces résultats de types types Python:

>>> hasattr("abc","__iter__")
False
>>> hasattr((0,),"__iter__")
True
>>> hasattr({},"__iter__")
True
>>> hasattr(set(),"__iter__")
True

Cela présente l’avantage supplémentaire de traiter une chaîne comme étant non-itérable - les chaînes sont une zone grise, car vous souhaitez parfois les traiter comme un élément, d’autres fois comme une séquence de caractères.

Notez que dans Python 3, le type str a l'attribut __ iter __ et que cela ne fonctionne pas:

>>> hasattr("abc", "__iter__")
True

Autres conseils

En règle générale, les chaînes (en clair et en unicode) sont les seuls itérables que vous souhaitez néanmoins considérer comme des "éléments uniques". - la structure basestring existe spécifiquement pour vous permettre de tester n'importe quel type de chaîne avec isinstance , de sorte qu'il est très sournois pour ce cas particulier; -).

Donc, mon approche suggérée pour le cas le plus général est la suivante:

  if isinstance(input, basestring): input = [input]
  else:
    try: iter(input)
    except TypeError: input = [input]
    else: input = list(input)

C’est LA façon de traiter CHAQUE CHAQUE itéreable SAUF les chaînes comme une liste directement, les chaînes et les nombres et les autres non-itérables comme des scalaires (à normaliser dans des listes à élément unique).

Je fais explicitement une liste de tous les types d'itérations afin que vous sachiez que vous pouvez effectuer TOUT type d'opérations sur les listes - trier, itérer plusieurs fois, ajouter ou supprimer des éléments pour faciliter l'itération, etc., sans altération. la liste de saisie ACTUAL (si liste en fait, c’était ;-). Si tout ce dont vous avez besoin est une simple boucle pour , cette dernière étape est inutile (voire inutile si, par exemple, input est un fichier ouvert énorme) et je suggérerais plutôt un générateur auxiliaire:

def justLoopOn(input):
  if isinstance(input, basestring):
    yield input
  else:
    try:
      for item in input:
        yield item
    except TypeError:
      yield input

maintenant, dans chacune de vos fonctions nécessitant une telle normalisation d'arguments, il vous suffit d'utiliser:

 for item in justLoopOn(input):

Vous pouvez utiliser une fonction de normalisation auxiliaire même dans l’autre cas (où vous avez besoin d’une liste réelle à des fins néfastes); en fait, dans de tels cas (plus rares), vous pouvez simplement faire:

 thelistforme = list(justLoopOn(input))

afin que la logique (inévitablement) de normalisation quelque peu poilue soit juste à UN endroit, comme il se doit! -)

Tout d’abord, il n’existe aucune méthode générale permettant de définir un "élément unique". à partir de " liste d'éléments " puisque par définition, liste peut être un élément d’une autre liste.

Je dirais que vous devez définir les types de données que vous pourriez avoir, afin que vous puissiez avoir:

  • tout descendant de list contre autre chose
    • Test avec isinstance (entrée, liste) (votre exemple est donc correct)
  • tout type de séquence sauf les chaînes ( basestring en Python 2.x, str en Python 3.x)
    • Utiliser la métaclasse de séquence: estinstance (myvar, collections.Sequence) et non une instance (myvar, str)
  • certains types de séquence par rapport aux cas connus, tels que int , str , MyClass
    • Test avec isinstance (entrée, (int, str, MyClass))
  • tout élément itérable sauf les chaînes:
    • Testez avec

.

    try: 
        input = iter(input) if not isinstance(input, str) else [input]
    except TypeError:
        input = [input]

Vous pouvez mettre * avant votre argument, ainsi vous aurez toujours un tuple:

def a(*p):
  print type(p)
  print p

a(4)
>>> <type 'tuple'>
>>> (4,)

a(4, 5)
>>> <type 'tuple'>
>>> (4,5,)

Mais cela vous obligera à appeler votre fonction avec des paramètres variables, je ne sais pas si cela vous convient.

Vous pouvez effectuer des comparaisons directes de types à l'aide de type () .

def my_func(input):
    if not type(input) is list:
        input = [input]
    for e in input:
        # do something

Toutefois, votre version actuelle permettra à tout type dérivé du type liste d'être transmis. Cela évite ainsi l’emballage accidentel des types dérivés.

Votre approche me semble juste.

Cela ressemble à la façon dont vous utilisez atom? dans Lisp lorsque vous parcourez des listes et vérifiez l'élément en cours pour voir s'il s'agit ou non d'une liste, car si vous voulez traiter cette liste ses articles aussi.

Alors, oui, je ne vois rien de mal à ça.

C’est une bonne façon de le faire (n'oubliez pas d’inclure des n-uplets).

Cependant, vous pouvez également décider si l'argument a une __iter__ méthode ou __getitem__ méthode . (notez que les chaînes ont __getitem__ au lieu de __iter __.)

hasattr(arg, '__iter__') or hasattr(arg, '__getitem__')

C’est probablement l’exigence la plus générale pour un type de type liste, à part vérifier uniquement le type.

Cela semble être un moyen raisonnable de le faire. Vous voulez tester si l'élément est une liste, et cela le fait directement. Cela devient plus compliqué si vous souhaitez prendre en charge d'autres types de données de type "liste", par exemple:

isinstance(input, (list, tuple))

ou plus généralement, résumez la question:

def iterable(obj):
  try:
    len(obj)
    return True
  except TypeError:
    return False

mais, encore une fois, votre méthode est simple et correcte, ce qui me semble bien!

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top