2009-08-13 22 views
16

siempre estoy molesto por este hecho:Volviendo Ninguno o una tupla y desembalaje

$ cat foo.py 
def foo(flag): 
    if flag: 
     return (1,2) 
    else: 
     return None 

first, second = foo(True) 
first, second = foo(False) 

$ python foo.py 
Traceback (most recent call last): 
    File "foo.py", line 8, in <module> 
    first, second = foo(False) 
TypeError: 'NoneType' object is not iterable 

El hecho es que con el fin de descomprimir correctamente sin dificultad que tengo ya sea para coger el TypeError o tener algo como

values = foo(False) 
if values is not None: 
    first, second = values 

Que es un poco molesto. ¿Hay algún truco para mejorar esta situación (por ejemplo, establecer primero y segundo a Ninguno sin tener que devolver foo (Ninguno, Ninguno)) o una sugerencia sobre la mejor estrategia de diseño para casos como el que presento? * variables tal vez?

Respuesta

16

Bueno, usted podría hacer ...

first,second = foo(True) or (None,None) 
first,second = foo(False) or (None,None) 

pero por lo que yo sé no hay manera más fácil de ampliar Ninguno de llenar la totalidad de una tupla.

+0

ooooh ... esto no lo consideré. enfoque muy "perverso", pero inteligente. –

+0

No estoy de acuerdo con que sea perl-ish. 'x o y' es una forma muy pitonica de decir' (x? x: y) 'o' (si x luego x else y) 'o' (si no x luego y) 'o como quiera que quieras explicarlo . –

+0

bien, acaba de salir de un "cantar" la frase típica de "lo que sea o muera()". –

7

No creo que haya un truco. Se puede simplificar el código de llamada a:

values = foo(False) 
if values: 
    first, second = values 

o incluso:

values = foo(False) 
first, second = values or (first_default, second_default) 

donde first_default y second_default son valores que desea dar al primer y segundo como valores por defecto.

+0

¿Qué versión de python es la segunda opción que funciona? v = (1,); x, y = v o (v, 2) –

18

No veo cuál es el problema al regresar (Ninguno, Ninguno). Es mucho más limpio que las soluciones sugeridas aquí que implican muchos más cambios en su código.

Tampoco tiene sentido que desee que None se divida automágicamente en 2 variables.

+1

Estaba en el proceso de pensar lo mismo. Estoy de acuerdo en que no tiene sentido: desde el punto de vista conceptual, regresar None w.r.t. una tupla de Nones es probablemente diferente (por supuesto, depende de la rutina real).Aquí estamos hablando de casos simulados) –

+2

+1 - absolutamente - es extraño hacer que la función devuelva una combinación tan sorprendente de resultados y tenga que lidiar con ella en la persona que llama. –

+1

@Alex: bueno, eso depende. En general, puede tener casos de rutinas que se supone que devuelven _x_ (con x = lo que sea) o None si no se encuentran. Si sabe que x es una tupla y una tupla de dos objetos, es un caso especial, pero el caso _x_ o Ninguno si no se encuentra aún es válido. El hecho de que lo desempaquete es ajeno a la rutina llamada. –

2

Debe tener cuidado con el estilo de solución x or y. Funcionan, pero son un poco más amplios que las especificaciones originales. Básicamente, ¿qué pasa si foo(True) devuelve una tupla vacía ()? Siempre que sepa que está bien tratarlo como (None, None), está de acuerdo con las soluciones proporcionadas.

Si esto fuera un escenario común, probablemente volvería a escribir una función de utilidad como:

# needs a better name! :) 
def to_tup(t): 
    return t if t is not None else (None, None) 

first, second = to_tup(foo(True)) 
first, second = to_tup(foo(False)) 
1
def foo(flag): 
    return ((1,2) if flag else (None, None)) 
14

Creo que hay un problema de la abstracción .

Una función debe mantener cierto nivel de abstracción, lo que ayuda a reducir la complejidad del código.
En este caso, o bien la función no mantiene la abstracción correcta, o bien la persona que llama no la está respetando.

La función podría haber sido algo así como get_point2d(); en este caso, el nivel de la abstracción está en la tupla y, por lo tanto, devolver None sería una buena manera de señalar algún caso particular (por ejemplo, una entidad no existente). El error en este caso sería esperar dos elementos, mientras que en realidad lo único que sabe es que la función devuelve un objeto (con información relacionada con un punto 2d).

Pero también podría haber sido algo así como get_two_values_from_db(); en este caso, la abstracción se rompería al devolver Ninguno, porque la función (como su nombre lo sugiere) debe devolver dos valores y no uno!

De cualquier forma, el objetivo principal de usar una función, reducir la complejidad, se pierde, al menos parcialmente.

Tenga en cuenta que este problema no aparece claramente con el nombre original; Es por eso que siempre es importante dar buenos nombres a la función y los métodos.

+1

gran comentario. Ojalá pudiera hacer +2 –

2

¿Qué tal esto:

$ cat foo.py 
def foo(flag): 
    if flag: 
     return (1,2) 
    else: 
     return (None,)*2 

first, second = foo(True) 
first, second = foo(False) 

Editar: Para que quede claro, el único cambio es reemplazar return None con return (None,)*2. Estoy extremadamente sorprendido de que nadie más haya pensado en esto. (O si lo han hecho, me gustaría saber por qué no lo usaron).

+3

Porque el OP dijo explícitamente "sin tener foo regresar (Ninguno, Ninguno)". 'return (None,) * 2' es lo mismo que' return (None, None) '. – mhawke

+0

¿Estás seguro de que eso es lo que significaba? Pensé que el OP solo quería evitar * escribir * la expresión larga 'return None, None' todo el tiempo. Mi manera de resolver ese problema. –

+0

Bueno, no lo resuelve completamente, por supuesto, pero sí lo reduce, y funciona mejor para tuplas más largas de un tamaño dado. –

1

OK, me gustaría volver (Ninguno, Ninguno), pero mientras estemos en whacko-land (heh), aquí hay una forma de usar una subclase de tupla. En el caso else, no devuelve None, sino que devuelve un contenedor vacío, que parece estar en el espíritu de las cosas. El "iterador" del contenedor desempaqueta ninguno cuando está vacío. Demuestra el protocolo de iteración de todos modos ...

probada usando v2.5.2:

class Tuple(tuple): 
    def __iter__(self): 
     if self: 
      # If Tuple has contents, return normal tuple iterator... 
      return super(Tuple, self).__iter__() 
     else: 
      # Else return a bogus iterator that returns None twice... 
      class Nonerizer(object): 
       def __init__(self): 
        self.x=0 
       def __iter__(self): 
        return self 
       def next(self): 
        if self.x < 2: 
         self.x += 1 
         return None 
        else: 
         raise StopIteration 
      return Nonerizer() 


def foo(flag): 
    if flag: 
     return Tuple((1,2)) 
    else: 
     return Tuple() # It's not None, but it's an empty container. 

first, second = foo(True) 
print first, second 
first, second = foo(False) 
print first, second 

salida es la deseada:

1 2 
None None 
+0

ovación de pie para el buen código-fu :) –

+0

'Nonerizer()' se puede reemplazar por 'itertools.repeat (None, 2)'. – augurar

+1

@augurar o simplemente 'iter ((None, None))', haciendo que todo el ejercicio no tenga sentido. –

0

he encontrado una solución para este problema:

Retorno Ninguno o devuelve un objeto.

Sin embargo, no desea tener que escribir una clase solo para devolver un objeto. Para ello, puede utilizar una tupla llamado

De esta manera:

from collections import namedtuple 
def foo(flag): 
if flag: 
    return None 
else: 
    MyResult = namedtuple('MyResult',['a','b','c'] 
    return MyResult._make([1,2,3]) 

Y luego:

result = foo(True) # result = True 
result = foo(False) # result = MyResult(a=1, b=2, c=3) 

y tiene acceso a los resultados como este:

print result.a # 1 
print result.b # 2 
print result.C# 3