2008-11-20 32 views
18

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 I no lo quiere quiere 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 a una secuencia para evitar una envoltura especial más tarde. Que podía hacer¿Forma correcta de detectar el parámetro de secuencia?

type(X) in (list, tuple)

pero puede haber otros tipos de secuencias que no estoy al tanto de ninguna clase, y base común.

-N.

Editar: Consulte 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.

+1

Tenga en cuenta que un objeto de tipo str también es un tipo de secuencia. –

+2

@pi: correcto, y ese es el verdadero problema aquí. Todas las respuestas "buenas" a continuación no toman eso en cuenta. – noamtm

+0

Hmm. De hecho, diría que fue porque realmente no definiste el problema. Si hubiera dicho cuál era inicialmente la tarea, es posible que haya obtenido respuestas más útiles de inmediato. –

Respuesta

5

The problem with all of the above mentioned ways is that str is considered a sequence (it's iterable, has getitem, etc.) yet it's usually treated as a single item.

For example, a function may accept an argument that can either be a filename or a list of filenames. What's the most Pythonic way for the function to detect the first from the latter?

Sobre la base de la pregunta revisada, parece que lo que quiere es algo más parecido a:

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) 

Esto no está garantizado para manejar todas , 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 elementos iterables.

+8

Como @Jim Brissom señaló en un comentario sobre [esta respuesta] (http://stackoverflow.com/questions/4237434/pythonic-way- to-verify-parameter-is-a-sequence-but-not-string/4237502 # 4237502) a una pregunta relacionada, las cadenas que no tienen un atributo '__iter__' era una discrepancia que se solucionó en Python 3, lo que hace que este enfoque sea hacia adelante- incompatible. – martineau

1

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. Por lo tanto, podría decir:

is_sequence = '__getslice__' in dir(X) 

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

Como mencionamos en el comentario, una cuestión es que una cadena es una secuencia, pero es probable que no desee tratarla como una sola. Podría agregar una prueba explícita de que el tipo no es str.

+1

hasattr (X, '__getslice__') es una forma más eficiente de hacer lo mismo – Moe

+1

__getslice__ es en realidad obsoleto: las rebanadas en los tipos de usuario se manejan mejor al aceptar objetos de corte en el método __getitem__, por lo que las cosas pueden ser slicables sin tener __getslice__. Creo que tampoco deberías necesitar ser transferible para ser considerado una secuencia. – Brian

-1

Puede pasar su parámetro en la función incorporada len() y comprobar 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.

se puede comprobar que un objeto es una cadena con el siguiente código:

x.__class__ == "".__class__ 
+2

Una mejor forma de probar la cadena es 'isinstance (x, basestr)'. –

+2

@Ned - Corrección secundaria: 'isinstance (x, basetring)' – Brian

1

respuesta Revisado:

no sé si su idea de "secuencia" coincide con lo que el pitón los manuales llaman "Sequence Type", pero en caso de que lo haga, debe buscar el método __Contains__. Ese es el método de Python usa para implementar la verificación "si algo en objeto:"

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

Mi respuesta original:

Me gustaría comprobar si el objeto que ha recibido implementa una interfaz iterador:

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

para mí, esa es la más cercana a la definición de la secuencia ya que ello permitirá hacer algo como:

for each in X: 
    print each 
+0

Los objetos de archivo tienen una interfaz de iterador. Puede haber otros objetos que también lo tengan y que no sean secuencias verdaderas. –

+0

@Dave: tiene razón sobre los objetos de archivo que tienen la interfaz del iterador. Añadiré una nueva respuesta revisada –

+0

Reitero un comentario que hice en otra publicación: debe usar hasattr (X, '__contains__') en lugar de '__contains__' en dir (X) – Moe

3

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í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.

4

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

Así secuencias no son los mismos que los objetos iterables. Creo que la secuencia debe implementar __getitem__, mientras que los objetos iterables deben implementar __iter__. Entonces, por ejemplo, las cadenas son secuencias y no implementan __iter__, los objetos 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) si desea secuencias, pero vaya más bien a hasattr("__iter__", X) si no quiere cadenas, por ejemplo.

+0

hasattr no es suficiente para verificar que sea iterable. La lista * tipo * también tendrá un miembro __iter__, pero no es iterable. es decir. hasattr (lista, '__ iter__') == Verdadero, pero iter (lista) -> "TipoError: 'tipo' objeto no es iterable". También perderá objetos que implementen __getitem__ pero no __iter__ – Brian

+0

Ok para la lista caso particular: es un builtin y __iter__ es un método estático (1 arg). Entonces, básicamente, sí, la escritura de pato falla. Pero iter() crea un objeto iterable a partir de uno no iterable (por lo que no es la solución). La única solución 'segura' es excluir/incluir tipos deseados como lo hicieron usted y noamtm. –

3

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

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

Si es necesario asegurarse de que es una secuencia de acceso puede reiniciar o al azar (es decir, no un generador, etc.), este enfoque no será suficiente sin embargo.

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

if not isinstance(X, basestring) 
+0

No me ayuda, porque un objeto str es iterable, pero quiero tratarlo como un solo elemento. – noamtm

+0

Es por eso que mencioné verificar isinstance. Dado que las cadenas son totalmente iterables e indexables, no hay forma de excluirlas en función de su "secuencia" que tampoco excluirá algún otro tipo de secuencia. Su única opción real es agregar una comprobación de instancia explícita para tratarlos como una excepción. – Brian

0

Estás haciendo la pregunta equivocada no intenta detectar los tipos en Python; observa un funcionamiento

  1. escribir otra función que maneja una sola.. valor. (llamémoslo _use_single_val).
  2. Escribe una función que maneje un parámetro de secuencia. (llamémoslo _use_sequence).
  3. Escriba una tercera función principal que llame a las dos anteriores. (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. Escribir pruebas de unidad para pasar correctamente los parámetros incorrectos a la función primaria 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" 

EDIT: Revisado para manejar la "secuencia o un valor único", preguntó acerca de la cuestión.

+0

No sé por qué se rechazó esta, creo que es la mejor pista aquí. El flujo de trabajo con un lenguaje de tipado dinámico no debe ser 'qué tipo es este objeto', sino '¿qué puede hacer este objeto?' Es lo mismo que probar navegadores para qué navegador son, en lugar de cómo se comportan en contextos. –

+0

¿No es eso lo que se pregunta? Él tiene un método basado en la detección de tipo (tipo (X) en (lista, tupla)), pero quiere uno que detectará en función del comportamiento (actúa como una secuencia). Tampoco son parámetros "incorrectos" para sus propósitos, solo quiere que hagan cosas diferentes. – Brian

+0

-1, estoy de acuerdo con el comportamiento de detección, pero la pregunta es sobre la sobrecarga según la secuencia/no secuencia – orip

20

A partir del 2.6, use abstract base classes.

>>> 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 secuencias 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 de la clase Atómica se puede utilizar con isinstance y issubclass:

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

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

Nótese que en Python 3 la sintaxis para especificar metaclases cambió y se eliminó el basestring superclase abstracta, 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 se desea, es posible escribir código que es compatible tanto con Python 2.6+ como con 3.x, pero hacerlo requiere una técnica un poco 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 lo mismo que la función six del módulo six 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 versiones antes de 2.6, hay damas de tipo en el módulo operator.

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

>>> assert operator.isSequenceType ("hello") == Verdadero (como se señaló en otro lugar, las cadenas son secuencias, y Coady estoy seguro de que sabe bien ... la pregunta original no estaba especificada. –

2

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 antes mencionadas es que str se considera una secuencia (es iterable, tiene __getitem__, etc.) Sin embargo, es generalmente considerado 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?

¿Debería publicar esto como una nueva pregunta? Edita el original?

+0

I [ La respuesta de Coady] (http://stackoverflow.com/a/306222/355230) es la mejor porque permite escoger y elegir qué clases se consideran secuencias o no, independientemente de los métodos especiales que puedan o no tener. – martineau

4

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

myfunc(item) 
myfunc(*items) 
+0

En realidad 'myfunc (* items)' pasa una tupla de la lista de elementos. – martineau

1

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

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