O argumento de desempacotamento usa iteração ou obtenção de itens?

Estou usando o Python 2.7.3.

Considere uma classe fictícia com comportamento customizado (embora ruim) e de obtenção de itens:

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

Faça um exemplo e veja o comportamento estranho:

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

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

>>> zz[0]
3

>>> zz[1]
3

Mas agora, vamos fazer uma função e, em seguida, fazer o argumento de desempacotamento emzz:

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!

Então, a descompactação de argumentos está de alguma forma recebendo os dados do itemzz mas não por qualquer iteração sobre o objeto com seu iterador implementado e também não fazendo iterador e chamando um homem pobre__getitem__ para quantos itens o objeto tiver.

Então a questão é: como a sintaxeadd3(*zz) adquirir os dados membros dezz se não por esses métodos? Estou faltando apenas um outro padrão comum para obter membros de dados de um tipo como este?

Meu objetivo é ver se eu poderia escrever uma classe que implementa a iteração ou a obtenção de itens de maneira que altere o significado da sintaxe de descompactação do argumento para essa classe. Depois de tentar o exemplo acima, agora estou me perguntando como o desempacotamento de argumentos ocorre nos dados subjacentes e se o programador pode influenciar esse comportamento. Google para isso só deu de volta um mar de resultados explicando o uso básico do*args sintaxe.

Eu não tenho um caso de uso para precisar fazer isso e não estou dizendo que é uma boa ideia. Eu só quero ver como fazer isso por curiosidade.

Adicionado

Como os tipos internos são tratados especialmente, aqui está um exemplo comobject onde eu apenas manter um objeto de lista e implementar o meu próprio get e definir o comportamento para emular a 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)

Nesse 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:

Mas, em vez disso, se eu garantir que a iteração pare e sempre retorne 3, posso obter o que eu estava gravando para brincar no primeiro 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)

Então eu vejo isso, que é o que eu esperava originalmente:

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]

Resumo

A sintaxe*args depende apenas de iteração. Para tipos incorporados isso acontece de uma maneira que não é diretamente sobreposta nas classes que herdam do tipo interno.

Estes dois são funcionalmente equivalentes:

foo(*[x for x in args])

foo(*args)

Estes não são equivalentes, mesmo para estruturas de dados finitas.

foo(*args)

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

questionAnswers(1)

yourAnswerToTheQuestion