¿Manera correcta de detectar el parámetro de secuencia?

Quiero escribir una función que acepte un parámetro que puede ser una secuencia o un solo valor. El tipo de valor es str, int, etc., pero no quiero que esté restringido a una lista codificada. En otras palabras, quiero saber si el parámetro X es una secuencia o algo que tengo que convertir en una secuencia para evitar una carcasa especial más adelante. Yo podría hacer

type(X) in (list, tuple)

pero puede que haya otros tipos de secuencia que no conozco y no una clase base común.

-NORTE.

Editar : vea mi “respuesta” a continuación para ver por qué la mayoría de estas respuestas no me ayudan. Quizás tengas algo mejor que sugerir.

El problema con todas las formas mencionadas anteriormente es que str se considera una secuencia (es iterable, tiene getitem , etc.) pero generalmente se trata como un solo elemento.

Por ejemplo, una función puede aceptar un argumento que puede ser un nombre de archivo o una lista de nombres de archivos. ¿Cuál es la forma más Pythonic para que la función detecte la primera de esta última?

Basado en la pregunta revisada, suena como que lo que quieres es algo más como:

 def to_sequence(arg): ''' determine whether an arg should be treated as a "unit" or a "sequence" if it's a unit, return a 1-tuple with the arg ''' def _multiple(x): return hasattr(x,"__iter__") if _multiple(arg): return arg else: return (arg,) >>> to_sequence("a string") ('a string',) >>> to_sequence( (1,2,3) ) (1, 2, 3) >>> to_sequence( xrange(5) ) xrange(5) 

No se garantiza que esto maneje todos los tipos, pero maneja los casos que usted menciona bastante bien, y debería hacer lo correcto para la mayoría de los tipos incorporados.

Cuando lo use, asegúrese de que todo lo que reciba el resultado de esto pueda manejar los resultados.

A partir del 2.6, use clases base abstractas .

 >>> import collections >>> isinstance([], collections.Sequence) True >>> isinstance(0, collections.Sequence) False 

Además, los ABC pueden personalizarse para tener en cuenta las excepciones, como no considerar que las cadenas sean secuencias. Aquí un ejemplo:

 import abc import collections class Atomic(object): __metaclass__ = abc.ABCMeta @classmethod def __subclasshook__(cls, other): return not issubclass(other, collections.Sequence) or NotImplemented Atomic.register(basestring) 

Después del registro, la clase Atomic se puede usar con isinstance y issubclass :

 assert isinstance("hello", Atomic) == True 

Esto es mucho mejor que una lista codificada, ya que solo necesita registrar las excepciones a la regla, y los usuarios externos del código pueden registrar las suyas.

Tenga en cuenta que en Python 3, la syntax para especificar las metaclases cambió y se basestring la superclase abstracta de basestring , lo que requiere que se use algo como lo siguiente:

 class Atomic(metaclass=abc.ABCMeta): @classmethod def __subclasshook__(cls, other): return not issubclass(other, collections.Sequence) or NotImplemented Atomic.register(str) 

Si lo desea, es posible escribir código que sea compatible con Python 2.6+ y 3.x, pero hacerlo requiere una técnica un poco más complicada que cree dinámicamente la clase base abstracta necesaria, evitando así errores de syntax debido a la diferencia de syntax de metaclase . Esto es esencialmente lo mismo que hace la función with_metaclass() los seis módulos de Benjamin Peterson.

 class _AtomicBase(object): @classmethod def __subclasshook__(cls, other): return not issubclass(other, collections.Sequence) or NotImplemented class Atomic(abc.ABCMeta("NewMeta", (_AtomicBase,), {})): pass try: unicode = unicode except NameError: # 'unicode' is undefined, assume Python >= 3 Atomic.register(str) # str includes unicode in Py3, make both Atomic Atomic.register(bytes) # bytes will also be considered Atomic (optional) else: # basestring is the abstract superclass of both str and unicode types Atomic.register(basestring) # make both types of strings Atomic 

En las versiones anteriores a 2.6, hay comprobadores de tipos en el módulo del operator .

 >>> import operator >>> operator.isSequenceType([]) True >>> operator.isSequenceType(0) False 

En mi humilde opinión, la forma de python es pasar la lista como * lista. Como en:

 myfunc(item) myfunc(*items) 

Las secuencias se describen aquí: https://docs.python.org/2/library/stdtypes.html#sequence-types-str-unicode-list-tuple-bytearray-buffer-xrange

Así que las secuencias no son lo mismo que los objetos iterables. Creo que la secuencia debe implementar __getitem__ , mientras que los objetos iterables deben implementar __iter__ . Así, por ejemplo, las cadenas son secuencias y no implementan __iter__ , los objetos xrange son secuencias y no implementan __getslice__ .

Pero por lo que viste que querías hacer, no estoy seguro de que quieras secuencias, sino más bien objetos iterables. Así que ve a hasattr("__getitem__", X) quieres secuencias, pero ve a hasattr("__iter__", X) si no quieres cadenas, por ejemplo.

En casos como este, prefiero simplemente tomar siempre el tipo de secuencia o siempre tomar el escalar. Las cadenas no serán los únicos tipos que se comportarán mal en esta configuración; más bien, cualquier tipo que tenga un uso agregado y permita una iteración sobre sus partes podría comportarse mal.

El método más simple sería verificar si puede convertirlo en un iterador. es decir

 try: it = iter(X) # Iterable except TypeError: # Not iterable 

Si necesita asegurarse de que sea una secuencia de acceso aleatorio o reiniciable (es decir, no un generador, etc.), este enfoque no será suficiente.

Como han señalado otros, las cadenas también son iterables, por lo que si necesita excluirlas (especialmente importante si recurre a través de los elementos, como lista (iter (‘a’)) vuelve a mostrar [‘a’], entonces es posible que deba excluir específicamente con ellos:

  if not isinstance(X, basestring) 

Soy nuevo aquí, así que no sé cuál es la forma correcta de hacerlo. Quiero responder a mis respuestas:

El problema con todas las formas mencionadas anteriormente es que str se considera una secuencia (es iterable, tiene __getitem__ , etc.) pero generalmente se trata como un solo elemento.

Por ejemplo, una función puede aceptar un argumento que puede ser un nombre de archivo o una lista de nombres de archivos. ¿Cuál es la forma más Pythonic para que la función detecte la primera de esta última?

¿Debo publicar esto como una nueva pregunta? Editar el original?

Creo que lo que haría es verificar si el objeto tiene ciertos métodos que indican que es una secuencia. No estoy seguro de si hay una definición oficial de qué hace una secuencia. Lo mejor que se me ocurre es que debe soportar el corte en rodajas. Así que podrías decir:

 is_sequence = '__getslice__' in dir(X) 

También puede comprobar la funcionalidad particular que va a utilizar.

Como lo señaló pi en el comentario, un problema es que una cadena es una secuencia, pero probablemente no quiera tratarla como una sola. Podría agregar una prueba explícita de que el tipo no es str.

Si las cadenas son el problema, detecte una secuencia y filtre el caso especial de cadenas:

 def is_iterable(x): if type(x) == str: return False try: iter(x) return True except TypeError: return False 

Respuesta revisada:

No sé si su idea de “secuencia” coincide con lo que los manuales de Python llaman un ” Tipo de secuencia “, pero en caso de que así sea, debe buscar el método __Contains__. Ese es el método que utiliza Python para implementar la verificación “si hay algo en el objeto:”

 if hasattr(X, '__contains__'): print "X is a sequence" 

Mi respuesta original:

Me gustaría comprobar si el objeto que recibió implementa una interfaz de iterador:

 if hasattr(X, '__iter__'): print "X is a sequence" 

Para mí, esa es la coincidencia más cercana a su definición de secuencia, ya que le permitiría hacer algo como:

 for each in X: print each 

Estás haciendo la pregunta equivocada. No intentas detectar tipos en Python; detectas el comportamiento

  1. Escribe otra función que maneje un solo valor. (Llamémoslo _use_single_val).
  2. Escribe una función que maneje un parámetro de secuencia. (Llamémoslo _use_sequence).
  3. Escribe una tercera función principal que llame a las dos anteriores. (llámalo use_seq_or_val). Rodee cada llamada con un controlador de excepciones para capturar un parámetro no válido (es decir, no un solo valor o secuencia).
  4. Escriba pruebas unitarias para pasar los parámetros correctos e incorrectos a la función principal para asegurarse de que detecte las excepciones correctamente.
 def _use_single_val(v): print v + 1 # this will fail if v is not a value type def _use_sequence(s): print s[0] # this will fail if s is not indexable def use_seq_or_val(item): try: _use_single_val(item) except TypeError: pass try: _use_sequence(item) except TypeError: pass raise TypeError, "item not a single value or sequence" 

EDITAR: Revisado para manejar la “secuencia o valor único” sobre la pregunta.

Podría pasar su parámetro en la función len () incorporada y verificar si esto causa un error. Como han dicho otros, el tipo de cadena requiere un manejo especial.

Según la documentación, la función len puede aceptar una secuencia (cadena, lista, tupla) o un diccionario.

Podría verificar que un objeto sea una cadena con el siguiente código:

 x.__class__ == "".__class__