我正在使用 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__
与对象一样多的项目。
那么问题来了:语法如何通过这些方法获取 if notadd3(*zz)
的数据成员?zz
我只是错过了从这样的类型获取数据成员的另一种常见模式吗?
我的目标是看看我是否可以编写一个实现迭代或获取项目的类,以改变参数解包语法对该类的含义。在尝试了上面的两个示例之后,我现在想知道参数解包如何获取基础数据以及程序员是否可以影响该行为。*args
为此,谷歌只返回了大量解释语法基本用法的结果。
我没有需要这样做的用例,我并不是说这是一个好主意。出于好奇,我只是想看看如何做到这一点。
添加
由于内置类型被特殊处理,这里有一个示例object
,我只维护一个列表对象并实现我自己的获取和设置行为来模拟列表。
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))])