【问题标题】:Change what the *splat and **splatty-splat operators do to my object更改 *splat 和 **splatty-splat 运算符对我的对象执行的操作
【发布时间】:2014-04-17 10:23:48
【问题描述】:

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

【问题讨论】:

  • 我 99% 肯定你不能......但很想在这里被证明是错误的(见 stackoverflow.com/questions/9722272/…
  • 您应该能够只实现可迭代或映射协议。不过,我在让映射正常工作时遇到了奇怪的问题。

标签: python iterable-unpacking splat double-splat


【解决方案1】:

* 迭代一个对象并将其元素用作参数。 ** 遍历对象的 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)...

【讨论】:

  • @wim:那么没有。这会非常混乱。
  • @wim:他们不是独立的运营商。它们是函数调用语法的一部分。您无法单独自定义它们,原因与您无法自定义将某些内容作为常规参数传递时发生的情况相同。
  • @wim:文档只是说“这是*** 所做的”,而不是“你不能让他们做其他事情”。我想我会去寻找源代码的哪一部分实现了相关的操作码。
  • 我看到了两个新魔法方法的 PEP:__splat____splatty_splat__,在正常情况下默认返回 __iter____iter__-with-__getitem__ .
  • @mapf:从技术上讲,它只在当前 CPython 实现中查找 keys__getitem__,但 language reference 指定解压缩参数必须是映射:“如果语法 @987654363 @ 出现在函数调用中,expression 必须评估为映射,其内容被视为附加关键字参数。”实现足以让** 获取您的对象是导致错误和混乱的秘诀,因此我故意将其排除在我的答案之外。
【解决方案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 - 所以它并没有像它假装做的那样真正压倒一切。

【讨论】:

  • 很容易让*thing**thing 至少总是按照你的意愿行事而不依赖于顺序——只需定义def keys(self): return ('hello world',)
  • 没有__len__返回1有什么特别的原因吗?另外,有什么理由需要扩展Mapping
  • @MadPhysicist 如果你不继承Mapping,你需要quack like a mapping。在Thing 的上下文中,这意味着我们必须定义一个keys 方法。如果你继承Mapping,你需要定义抽象方法__len__,但我不在乎它在这里返回什么——只是名称解析。
  • @wim。我一直认为您只需 __len__ 返回 1 就可以逃脱惩罚。很有趣。
猜你喜欢
  • 2020-03-13
  • 2013-07-04
  • 1970-01-01
  • 2014-12-13
  • 2015-03-13
  • 2017-07-09
  • 1970-01-01
相关资源
最近更新 更多