tutorial español desde cero python types sequences

python - español - ¿Forma correcta de detectar el parámetro de secuencia?



django python 3 (12)

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 de hardcoded. En otras palabras, quiero saber si el parámetro X es una secuencia o algo que tengo que convertir a una secuencia para evitar una envoltura especial más tarde. Yo podría hacer

type(X) in (list, tuple)

pero puede haber otros tipos de secuencia que no conozco, y ninguna clase base común.

-NORTE.

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


Si las cadenas son el problema, detectar una secuencia y filtrar el caso especial de cadenas:

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


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 lo que hace una secuencia. Lo mejor que puedo pensar es que debe admitir cortar. Entonces podrías decir:

is_sequence = ''__getslice__'' in dir(X)

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

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


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


Puede pasar su parámetro en la función incorporada len () y verificar si esto causa un error. Como otros dijeron, el tipo de cuerda requiere un manejo especial.

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

Puede verificar que un objeto sea una cadena con el siguiente código:

x.__class__ == "".__class__


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 reiniciable o aleatorio (es decir, no un generador, etc.), este enfoque no será suficiente.

Como otros han notado, las cadenas también son iterables, por lo que si necesita excluirlas (particularmente importante si recurre a través de elementos, como list (iter (''a'')) da de nuevo [''a''], entonces es posible que deba excluir específicamente con ellos:

if not isinstance(X, basestring)


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

myfunc(item) myfunc(*items)


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

  1. Escriba 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 padre que llame a las dos de arriba. (llámalo use_seq_or_val). Rodee cada llamada con un manejador de excepciones para capturar un parámetro invá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 capta 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 el que se pregunta en la pregunta.


El problema con todas las formas mencionadas anteriormente es que str se considera una secuencia (es iterable, tiene getitem , etc.) aunque 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 pitonica para que la función detecte la primera de la última?

Basado en la pregunta revisada, parece 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 está garantizado que maneje todos los tipos, pero maneja los casos que menciona bastante bien, y debería hacer lo correcto para la mayoría de los tipos incorporados.

Al usarlo, asegúrese de que todo lo que recibe el resultado de esto puede manejar los iterables.


A partir del 2.6, usa clases base abstractas .

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

Además, el ABC se puede personalizar para tener en cuenta las excepciones, como no considerar las cadenas como 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 utilizar con isinstance y issubclass :

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

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

Tenga en cuenta que en Python 3 la sintaxis para especificar las metaclases cambió y la superclase abstracta de basestring se eliminó, lo que requiere algo como lo siguiente para ser utilizado en su lugar:

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 tanto con Python 2.6+ como con 3.x, pero para hacerlo necesita usar una técnica ligeramente más complicada que crea dinámicamente la clase base abstracta necesaria, evitando así los errores de sintaxis debidos a la diferencia de sintaxis de la metaclase . Esto es esencialmente igual a lo que hace la función de with_metaclass() la with_metaclass() 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 verificadores de tipos en el módulo de operator .

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


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

Entonces 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__ . Entonces, por ejemplo, string son secuencias y no implementan __iter__ , los objetos de xrange son secuencias y no implementan __getslice__ .

Pero por lo que has visto que quieres hacer, no estoy seguro de que quieras secuencias, sino más bien objetos iterables. Así que vaya por hasattr("__getitem__", X) quiere secuencias, pero vaya más bien hasattr("__iter__", X) si no quiere cadenas, por ejemplo.


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 lo haga, debe buscar el método __Contains__. Ese es el método que Python usa para implementar la comprobación "si hay algo en el objeto:"

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

Mi respuesta original:

Verificaría si el objeto que recibiste implementa una interfaz de iterador:

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

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

for each in X: print each


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

El problema con todas las formas mencionadas anteriormente es que str se considera una secuencia (es iterable, tiene __getitem__ , etc.) aunque 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 pitonica para que la función detecte la primera de la última?

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