2010-01-21 18 views
9

Estoy utilizando una función de biblioteca de terceros que lee un conjunto de palabras clave de un archivo y se supone que devuelve una tupla de valores. Lo hace correctamente siempre que haya al menos dos palabras clave. Sin embargo, en el caso donde solo hay una palabra clave, devuelve una cadena sin formato, no una tupla del tamaño uno. Esto es particularmente pernicioso porque cuando trato de hacer algo como¿Cuál es la mejor práctica para manejar tuplas de valor único en Python?

for keyword in library.get_keywords(): 
    # Do something with keyword 

, en el caso de la palabra clave, los for itera sobre cada carácter de la cadena en la serie, lo que arroja una excepción, en tiempo de ejecución o de lo contrario, pero sin embargo es completamente inútil para mí.

Mi pregunta es doble:

Es evidente que esto es un error en la biblioteca, que está fuera de mi control. ¿Cómo puedo evitarlo?

En segundo lugar, en general, si estoy escribiendo una función que devuelve una tupla, ¿cuál es la mejor práctica para garantizar que las tuplas con un elemento se generan correctamente? Por ejemplo, si tengo

def tuple_maker(values): 
    my_tuple = (values) 
    return my_tuple 

for val in tuple_maker("a string"): 
    print "Value was", val 

for val in tuple_maker(["str1", "str2", "str3"]): 
    print "Value was", val 

Puedo obtener

Value was a 
Value was 
Value was s 
Value was t 
Value was r 
Value was i 
Value was n 
Value was g 
Value was str1 
Value was str2 
Value was str3 

¿Cuál es la mejor manera de modificar la función my_tuple para volver realidad una tupla cuando sólo hay un único elemento? ¿Debo explícitamente verificar si el tamaño es 1 y crear la tupla por separado, utilizando la sintaxis (value,)? Esto implica que cualquier función que tenga la posibilidad de devolver una tupla de un solo valor debe hacer esto, lo que parece raro y repetitivo.

¿Hay alguna solución general elegante a este problema?

+0

No creo que sea "ciertamente" un error. Posiblemente, pero posiblemente su comportamiento previsto (por supuesto, si los documentos dicen que siempre debe devolver una tupla, es un error :) Las partes IIRC del módulo 're' devolverán un elemento individual si solo hay una coincidencia de expresiones regulares, o una tupla de ellos si hay más de uno. –

+5

En general, la comunidad de Python acepta como una mala práctica permitir un valor simple en lugar de 1-tupla, debido a la experiencia negativa con lugares como ese y el operador '%'. Me gustaría presentar un error. – bobince

+2

Es un error o estúpido. Tu eliges. ;) –

Respuesta

14

Debe probar de alguna manera el tipo, si es una cadena o una tupla.Yo lo haría así:

keywords = library.get_keywords() 
if not isinstance(keywords, tuple): 
    keywords = (keywords,) # Note the comma 
for keyword in keywords: 
    do_your_thang(keyword) 
+0

Esto funciona, siempre que las listas se supone que están envueltos en tuplas únicas. – jcdyer

+0

@jcd: Ellos son. Ver la primera oración de la pregunta. –

+1

Ok, gracias. Estaba receloso de verificar el tipo, pero no parece haber mucha alternativa. Probablemente ajustaré la función de la biblioteca con un contenedor como este, y lo llamaré desde mi propio código. Y generar un error con el mantenedor de la biblioteca. ;) –

0

para su primer problema se puede comprobar si el valor de retorno es tupla usando

type(r) is tuple 
#alternative 
isinstance(r, tuple) 
# one-liner 
def as_tuple(r): return [ tuple([r]), r ][type(r) is tuple] 

la segunda cosa que me gusta usar tuple([1]). creo que es una cuestión de gusto. probablemente también podría escribir un contenedor, por ejemplo def tuple1(s): return tuple([s])

1

En lugar de buscar una longitud de 1, usaría la instancia incorporada en su lugar.

>>> isinstance('a_str', tuple) 
False 
>>> isinstance(('str1', 'str2', 'str3'), tuple) 
True 
6

Para su primer problema, no estoy realmente seguro de si esta es la mejor respuesta, pero creo que es necesario comprobar si el mismo valor devuelto es una cadena o tupla y actuar en consecuencia.

En cuanto a su segundo problema, cualquier variable se puede convertir en una sola tupla valorada mediante la colocación de un , junto a él:

>>> x='abc' 
>>> x 
'abc' 
>>> tpl=x, 
>>> tpl 
('abc',) 

Poner estas dos ideas:

>>> def make_tuple(k): 
...  if isinstance(k,tuple): 
...    return k 
...  else: 
...    return k, 
... 
>>> make_tuple('xyz') 
('xyz',) 
>>> make_tuple(('abc','xyz')) 
('abc', 'xyz') 

Nota: En mi humilde opinión, generalmente es una mala idea usar isinstance o cualquier otra forma de lógica que necesite verificar el tipo de un objeto en tiempo de ejecución. Pero para este problema no veo ninguna forma de evitarlo.

1

Su tuple_maker no hace lo que usted cree. Una definición equivalente de tuple maker a la suya es

def tuple_maker(input): 
    return input 

Lo que están viendo es que tuple_maker("a string") devuelve una cadena, mientras que tuple_maker(["str1","str2","str3"]) devuelve una lista de cadenas; ¡ninguno devuelve una tupla!

Las tuplas en Python se definen por la presencia de comas, no de corchetes. Por lo tanto, (1,2) es una tupla que contiene los valores 1 y 2, mientras que (1,) es una tupla que contiene el valor único 1.

Para convertir un valor en una tupla, como han señalado otros, use tuple.

>>> tuple([1]) 
(1,) 
>>> tuple([1,2]) 
(1,2) 
+1

'tuple()' crea una tupla de cualquier iterable; no tiene ningún valor. 'tuple ('abc') ==> ('a', 'b', 'c')', 'tuple (3) ==> error!' – Javier

+0

@me_and: Gracias por la aclaración sobre la definición de tupla. Pero Javier destaca el problema que estoy teniendo muy bien (ya había intentado usar 'tuple' antes de publicarlo aquí, y descubrí que no me ayuda). –

1

¿Es absolutamente necesario que devuelva tuplas, o lo hará alguna iterable?

import collections 
def iterate(keywords): 
    if not isinstance(keywords, collections.Iterable): 
     yield keywords 
    else: 
     for keyword in keywords: 
      yield keyword 


for keyword in iterate(library.get_keywords()): 
    print keyword 
+0

@Epcylon: +1 - Esta es una idea interesante para mis propias funciones, pero no me ayuda con mi biblioteca de terceros. –

2

Siempre hay monkeypatching!

# Store a reference to the real library function 
really_get_keywords = library.get_keywords 

# Define out patched version of the function, which uses the real 
# version above, adjusting its return value as necessary 
def patched_get_keywords(): 
    """Make sure we always get a tuple of keywords.""" 
    result = really_get_keywords() 
    return result if isinstance(result, tuple) else (result,) 

# Install the patched version 
library.get_keywords = patched_get_keywords 

NOTA: Este código podría incendiar su casa y dormir con su esposa.

+0

@Will McCutchen: +1 - ¡Un buen punto!Pero creo que será más seguro envolver, pero no instalar, la versión parchada. No sé lo suficiente sobre las partes internas de la biblioteca para garantizar que no romperé otra cosa que dependa de este comportamiento. –

Cuestiones relacionadas