17

你如何覆盖解包语法的结果*obj**obj

例如,您能否以某种方式创建一个thing行为如下的对象:

>>> [*thing]
['a', 'b', 'c']
>>> [x for x in thing]
['d', 'e', 'f']
>>> {**thing}
{'hello world': 'I am a potato!!'}

注意:通过 ("for x in thing") 的迭代__iter__从 *splat 解包返回不同的元素。

我看过operator.muland operator.pow,但这些函数只涉及两个操作数的用法,比如a*band a**b,并且似乎与 splat 操作无关。

4

2 回答 2

24

*迭代一个对象并将其元素用作参数。**遍历一个对象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))在任何情况下,*并且**是函数调用语法的一部分,而不是单独的运算符,因此自定义它们(如果可能)将是可调用的工作,而不是参数的工作。我想可能存在允许可调用对象自定义它们的用例,也许可以传递dict子类,例如defaultdict...

于 2014-03-12T23:20:29.360 回答
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()实例本身不是迭代器,尽管它是可迭代的)。映射协议由 和 的存在来keys()满足__getitem__。然而,如果它还不是很明显,你不能连续调用*thing两次并让它连续解压a,b,c两次——所以它并没有像它假装做的那样真正覆盖 splat。

于 2014-03-13T03:08:26.383 回答