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

используя 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)
 in ()
----> 1 [x for x in zz]

 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)
 in ()
----> 1 add_3(*zz)

 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)
 in ()
----> 1 ix.next()

 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)
 in ()
----> 1 ix.next()

 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)

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