如何覆盖解压缩语法*obj
和**obj
的结果?
例如,你能不能以某种方式创建一个对象thing
,其行为如下:
>>> [*thing]
['a', 'b', 'c']
>>> [x for x in thing]
['d', 'e', 'f']
>>> {**thing}
{'hello world': 'I am a potato!!'}
注意: 通过__iter__
进行迭代("对于x中的事物")返回* splat中的不同元素解包。
我查看了operator.mul
和operator.pow
,但这些函数仅涉及两个操作数的用法,例如a*b
和a**b
,并且似乎与splat操作无关。
答案 0 :(得分:17)
*
遍历一个对象并使用其元素作为参数。 **
遍历对象的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) {
,如果**
参数不是dict,则创建一个dict并执行普通update
以从关键字参数初始化它(除了PyDict_Update
不接受键值对列表)。因此,您无法单独定制**
以实现映射协议。
同样,对于*
个参数,ext_do_call
执行
if (!PyTuple_Check(stararg)) {
PyObject *t = NULL;
t = PySequence_Tuple(stararg);
相当于tuple(args)
。因此,您无法与普通迭代分开地自定义*
。
如果f(*thing)
和f(*iter(thing))
做了不同的事情,那将会非常令人困惑。在任何情况下,*
和**
都是函数调用语法的一部分,而不是单独的运算符,因此自定义它们(如果可能)将是可调用的作业,而不是参数。我想可能有一些用例允许callable自定义它们,也许是为了dict
通过defaultdict
子类来传递...
答案 1 :(得分:2)
我确实成功制作了一个与我在问题中描述的行为相似的物体,但我真的不得不作弊。所以只是在这里发布这个很有趣,真的 -
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()
实例本身不是迭代器,尽管它是 iterable < / em>的)。 keys()
和__getitem__
的存在满足了映射协议。然而,如果它还不是很明显,你不能连续两次调用*thing
并让它连续两次解包a,b,c
- 所以它并没有真正覆盖splat,就像假装它一样做。