将* splat和** splatty-splat运算符更改为我的对象

时间:2014-03-12 23:15:07

标签: python iterable-unpacking splat double-splat

如何覆盖解压缩语法*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.muloperator.pow,但这些函数仅涉及两个操作数的用法,例如a*ba**b,并且似乎与splat操作无关。

2 个答案:

答案 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,就像假装它一样做。