如何重写解包语法*obj
和**obj
的结果?
例如,你能不能创建一个对象thing
,它的行为是这样的:
>>> [*thing]
['a', 'b', 'c']
>>> [x for x in thing]
['d', 'e', 'f']
>>> {**thing}
{'hello world': 'I am a potato!!'}
注意:通过__iter__
的迭代("for x in thing")返回与*splat解包不同的元素。
我看了看operator.mul
和operator.pow
,但这些函数只关注两个操作数的用法,如a*b
和a**b
,似乎与splat操作无关。
*
遍历对象并使用其元素作为参数。**
迭代对象的keys
,并使用__getitem__
(相当于括号表示法)获取键值对。要自定义*
,只需使对象可迭代;要自定义**
,则使对象成为映射:
class MyIterable(object):
def __iter__(self):
return iter([1, 2, 3])
class MyMapping(collections.Mapping):
def __iter__(self):
return iter('123')
def __getitem__(self, item):
return int(item)
def __len__(self):
return 3
如果你想让*
和**
做一些之外的上面描述的事情,你不能。我没有关于这句话的文档参考(因为找到"你可以这样做"的文档更容易)。而不是"你不能这样做",但我有一个来源引用。PyEval_EvalFrameEx
中的字节码解释器循环调用ext_do_call
来实现带有*
或**
参数的函数调用。ext_do_call
包含以下代码:
if (!PyDict_Check(kwdict)) {
PyObject *d;
d = PyDict_New();
if (d == NULL)
goto ext_call_fail;
if (PyDict_Update(d, kwdict) != 0) {
,如果**
参数不是字典,则创建一个字典,并执行普通的update
从关键字参数初始化它(除了PyDict_Update
不接受键值对列表)。因此,您不能将自定义**
与实现映射协议分开。
*
参数,ext_do_call
执行
if (!PyTuple_Check(stararg)) {
PyObject *t = NULL;
t = PySequence_Tuple(stararg);
相当于tuple(args)
。因此,您不能从普通迭代中单独定制*
。
如果f(*thing)
和f(*iter(thing))
做不同的事情,那将是非常令人困惑的。在任何情况下,*
和**
都是函数调用语法的一部分,而不是单独的操作符,因此自定义它们(如果可能的话)将是可调用对象的工作,而不是参数的工作。我想可能有允许可调用自定义它们的用例,也许通过dict
子类如defaultdict
…
我确实成功地创造了一个符合我在问题中描述的行为的对象,但我真的不得不作弊。所以在这里发布只是为了好玩,真的-
class Thing:
def __init__(self):
self.mode = 'abc'
def __iter__(self):
if self.mode == 'abc':
yield 'a'
yield 'b'
yield 'c'
self.mode = 'def'
else:
yield 'd'
yield 'e'
yield 'f'
self.mode = 'abc'
def __getitem__(self, item):
return 'I am a potato!!'
def keys(self):
return ['hello world']
迭代器协议由__iter__
返回的生成器对象来满足(注意Thing()
实例本身不是迭代器,尽管它是可迭代的)。如果存在keys()
和__getitem__
,则满足映射协议。然而,如果它还不是很明显,你不能在一行中调用*thing
两次,并让它在一行中解包a,b,c
两次——所以它并没有像它假装的那样真正覆盖splat。