Распаковка аргументов использует итерацию или получение элементов?

Я использую Python 2.7.3.

Рассмотрим фиктивный класс с пользовательской (хотя и плохой) итерацией и поведением получения элементов:

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

Сделайте пример и посмотрите странное поведение:

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

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

>>> zz[0]
3

>>> zz[1]
3

Но теперь давайте создадим функцию, а затем распакуем аргументzz:

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!

Таким образом, распаковка аргумента как-то получает данные элемента изzz но не итерацией по объекту с его реализованным итератором, а также не итератором и вызовом бедняков__getitem__ для столько предметов, сколько имеет объект.

Итак, вопрос: как работает синтаксис?add3(*zz) получить данные членовzz если не этими методами? Я просто пропускаю еще один распространенный шаблон для получения элементов данных из такого типа?

Моя цель - посмотреть, смогу ли я написать класс, реализующий итерацию или получение элементов таким образом, чтобы он изменил значение синтаксиса распаковки аргументов для этого класса. Попробовав два приведенных выше примера, я теперь задаюсь вопросом, как распаковка аргументов влияет на базовые данные и может ли программист влиять на это поведение. Google для этого только дал море результатов, объясняющих основное использование*args синтаксис.

У меня нет варианта использования для этого, и я не утверждаю, что это хорошая идея. Я просто хочу посмотреть, как это сделать ради любопытства.

добавленной

Поскольку встроенные типы обрабатываются специально, вот пример сobject где я просто поддерживаю объект списка и реализую свое собственное поведение get и set для эмуляции списка.

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)

В этом случае,

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:

Но вместо этого, если я гарантирую, что итерация останавливается и всегда возвращает 3, я могу получить то, с чем стрелял, чтобы поиграться в первом случае:

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)

Затем я вижу то, что я изначально ожидал:

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]

Резюме

Синтаксис*args полагается только на итерацию. Для встроенных типов это происходит способом, который не может быть напрямую переопределен в классах, которые наследуются от встроенного типа.

Эти два функционально эквивалентны:

foo(*[x for x in args])

foo(*args)

Они не эквивалентны даже для конечных структур данных.

foo(*args)

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

Ответы на вопрос(1)

Ваш ответ на вопрос