¿El desempaquetado de argumentos usa iteración o obtención de elementos?

Estoy usando Python 2.7.3.

Considere una clase ficticia con iteración personalizada (aunque sea mala) y comportamiento de obtención de elementos:

class FooList(list):
    def __iter__(self):
        return iter(self)
    def next(self):
        return 3
    def __getitem__(self, idx):
        return 3

Haz un ejemplo y observa el extraño comportamiento:

>>> zz = FooList([1,2,3])

>>> [x for x in zz]
# Hangs because of the self-reference in `__iter__`.

>>> zz[0]
3

>>> zz[1]
3

Pero ahora, hagamos una función y luego analicemos el desempaquetado enzz:

def add3(a, b, c):
    return a + b + c

>>> add3(*zz)
6
# I expected either 9 or for the interpreter to hang like the comprehension!

Por lo tanto, el desempaquetado de argumentos es de alguna manera obtener los datos del artículo dezz pero no por iterar sobre el objeto con su iterador implementado y tampoco por hacer el iterador de un hombre pobre y llamar__getitem__ por tantos elementos como el objeto tiene.

Entonces la pregunta es: ¿cómo funciona la sintaxis?add3(*zz) adquirir los datos de los miembros dezz Si no es por estos métodos? ¿Me estoy perdiendo otro patrón común para obtener miembros de datos de un tipo como este?

Mi objetivo es ver si puedo escribir una clase que implemente iteración o obtención de elementos de tal manera que cambie lo que significa el argumento de desempaquetado de sintaxis para esa clase. Después de probar los dos ejemplos anteriores, ahora me pregunto cómo el desempaque de argumentos llega a los datos subyacentes y si el programador puede influir en ese comportamiento. Google para esto solo devolvió un mar de resultados que explican el uso básico del*args sintaxis.

No tengo un caso de uso por la necesidad de hacer esto y no estoy diciendo que sea una buena idea. Solo quiero ver cómo hacerlo por curiosidad.

Adicional

Dado que los tipos incorporados se tratan especialmente, he aquí un ejemplo conobject donde solo mantengo un objeto de lista e implemento mi propio comportamiento de obtención y configuración para emular la lista.

class FooList(object):
    def __init__(self, lst):
        self.lst = lst
    def __iter__(self): raise ValueError
    def next(self): return 3
    def __getitem__(self, idx): return self.lst.__getitem__(idx)
    def __setitem__(self, idx, itm): self.lst.__setitem__(idx, itm)

En este caso,

In [234]: zz = FooList([1,2,3])

In [235]: [x for x in zz]
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-235-ad3bb7659c84> in <module>()
----> 1 [x for x in zz]

<ipython-input-233-dc9284300db1> in __iter__(self)
      2     def __init__(self, lst):
      3         self.lst = lst
----> 4     def __iter__(self): raise ValueError
      5     def next(self): return 3
      6     def __getitem__(self, idx): return self.lst.__getitem__(idx)

ValueError:

In [236]: add_3(*zz)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-236-f9bbfdc2de5c> in <module>()
----> 1 add_3(*zz)

<ipython-input-233-dc9284300db1> in __iter__(self)
      2     def __init__(self, lst):
      3         self.lst = lst
----> 4     def __iter__(self): raise ValueError
      5     def next(self): return 3
      6     def __getitem__(self, idx): return self.lst.__getitem__(idx)

ValueError:

Pero en cambio, si me aseguro de que la iteración se detenga y siempre devuelva 3, puedo hacer que jueguen con lo que estaba fotografiando en el primer caso:

class FooList(object):
    def __init__(self, lst):
        self.lst = lst
        self.iter_loc = -1
    def __iter__(self): return self
    def next(self): 
        if self.iter_loc < len(self.lst)-1:
            self.iter_loc += 1
            return 3
        else:
            self.iter_loc = -1
            raise StopIteration
    def __getitem__(self, idx): return self.lst.__getitem__(idx)
    def __setitem__(self, idx, itm): self.lst.__setitem__(idx, itm)

Entonces veo esto, que es lo que originalmente esperaba:

In [247]: zz = FooList([1,2,3])

In [248]: ix = iter(zz)

In [249]: ix.next()
Out[249]: 3

In [250]: ix.next()
Out[250]: 3

In [251]: ix.next()
Out[251]: 3

In [252]: ix.next()
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-252-29d4ae900c28> in <module>()
----> 1 ix.next()

<ipython-input-246-5479fdc9217b> in next(self)
     10         else:
     11             self.iter_loc = -1
---> 12             raise StopIteration
     13     def __getitem__(self, idx): return self.lst.__getitem__(idx)
     14     def __setitem__(self, idx, itm): self.lst.__setitem__(idx, itm)

StopIteration:

In [253]: ix = iter(zz)

In [254]: ix.next()
Out[254]: 3

In [255]: ix.next()
Out[255]: 3

In [256]: ix.next()
Out[256]: 3

In [257]: ix.next()
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-257-29d4ae900c28> in <module>()
----> 1 ix.next()

<ipython-input-246-5479fdc9217b> in next(self)
     10         else:
     11             self.iter_loc = -1
---> 12             raise StopIteration
     13     def __getitem__(self, idx): return self.lst.__getitem__(idx)
     14     def __setitem__(self, idx, itm): self.lst.__setitem__(idx, itm)

StopIteration:

In [258]: add_3(*zz)
Out[258]: 9

In [259]: zz[0]
Out[259]: 1

In [260]: zz[1]
Out[260]: 2

In [261]: zz[2]
Out[261]: 3

In [262]: [x for x in zz]
Out[262]: [3, 3, 3]

Resumen

La sintaxis*args se basa en la iteración solamente. Para los tipos incorporados, esto sucede de una manera que no se puede anular directamente en las clases que heredan del tipo incorporado.

Estos dos son funcionalmente equivalentes:

foo(*[x for x in args])

foo(*args)

Estos no son equivalentes incluso para estructuras de datos finitos.

foo(*args)

foo(*[args[i] for i in range(len(args))])

Respuestas a la pregunta(1)

Su respuesta a la pregunta