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.
- Escriba otra función que maneje un solo valor. (llamémoslo _use_single_val).
- Escribe una función que maneje un parámetro de secuencia. (llamémoslo _use_sequence).
- 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).
- 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?