Распаковка аргументов использует итерацию или получение элементов?
используя 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))])