参数解包是使用迭代还是项目获取?

ely*_*ely 12 python iterator arguments

我正在使用Python 2.7.3.

考虑一个带有自定义(虽然很糟糕)迭代和项目获取行为的虚拟类:

class FooList(list):
    def __iter__(self):
        return iter(self)
    def next(self):
        return 3
    def __getitem__(self, idx):
        return 3
Run Code Online (Sandbox Code Playgroud)

举个例子,看看奇怪的行为:

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

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

>>> zz[0]
3

>>> zz[1]
3
Run Code Online (Sandbox Code Playgroud)

但现在,让我们创建一个函数,然后执行参数解包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!
Run Code Online (Sandbox Code Playgroud)

因此,参数解包以某种方式获取项数据,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)
Run Code Online (Sandbox Code Playgroud)

在这种情况下,

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:
Run Code Online (Sandbox Code Playgroud)

但相反,如果我确保迭代停止并且总是返回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)
Run Code Online (Sandbox Code Playgroud)

然后我看到了这个,这是我最初的预期:

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]
Run Code Online (Sandbox Code Playgroud)

摘要

  1. 语法*args仅依赖于迭代.对于内置类型,这种方式在从内置类型继承的类中不能直接覆盖.

  2. 这两个在功能上是等价的:

    foo(*[x for x in args])

    foo(*args)

  3. 即使对于有限数据结构,这些也不相等.

    foo(*args)

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

Bre*_*arn 11

你被Python最令人恼火的疣之一所困扰:它们的内置类型和子类在某些地方被神奇地处理.

由于您的类型是子类list,Python会神奇地进入其内部以解压缩它.它根本不使用真正的迭代器API.如果将print内部的语句next__getitem__,你会发现没有一个是被称为.此行为无法被覆盖; 相反,你必须编写自己的类来重新实现内置类型.你可以尝试使用UserList; 我没有检查是否可行.

你的问题的答案是参数解包使用迭代.但是,__getitem__如果没有明确__iter__定义,迭代本身就可以使用.您不能创建一个定义与正常迭代行为不同的参数解包行为的类.

迭代器协议(基本上" __iter__工作原理")不应该假定适用于类似内置类型的类型list.如果你对内置类进行子类化,那么在某些情况下,你的子类可能会神奇地表现为底层内置函数,而不会使用自定义魔术方法(如__iter__).如果要完全可靠地自定义行为,则不能从内置类型子类化(当然,除外object).

  • @EMS:你为什么要这样?如果你不想要`list` /`tuple` /`dict`魔术行为,不要继承它们; 你可以保留一个`list` /`tuple` /`dict`成员并委托给它(确切地说你想要的地方,而不是你不想要的地方). (2认同)
  • 这是完全正确的.我想知道自己能做些什么,不是因为我想这么做,只是为了知道什么是可能的. (2认同)