66

我可以在 python 中为其他 dicts 的 dict 理解扩展语法,比如collections模块中的 OrderedDict 或我自己的继承自的类型dict

只是重新绑定dict名称显然是行不通的,{key: value}理解语法仍然为您提供了一个简单的旧字典,用于理解和文字。

>>> from collections import OrderedDict
>>> olddict, dict = dict, OrderedDict
>>> {i: i*i for i in range(3)}.__class__
<type 'dict'>

那么,如果可能的话,我将如何去做呢?如果它只适用于 CPython 就可以了。对于语法,我想我会尝试使用O{k: v}前缀,就像我们在r'various' u'string' b'objects'.

注意: 当然我们可以使用生成器表达式,但我更感兴趣的是看看 python 在语法方面是如何被破解的。

4

3 回答 3

95

对不起,不可能。字典文字和字典推导映射到内置的字典类型,以一种在 C 级别硬编码的方式。这不能被覆盖。

不过,您可以使用它作为替代方案:

OrderedDict((i, i * i) for i in range(3))

附录:从 Python 3.6 开始,所有 Python 字典都是有序的。从 3.7开始,它甚至是语言规范的一部分。如果您使用的是这些版本的 Python,则不需要 OrderedDict:dict 理解将 Just Work (TM)。

于 2014-01-14T00:03:03.373 回答
32

没有直接的方法可以从语言内部更改 Python 的语法。字典理解(或普通显示)总是会创建一个dict,对此您无能为力。如果您使用的是 CPython,它会使用直接生成 dict 的特殊字节码,最终调用PyDictAPI 函数和/或该 API 使用的相同底层函数。如果您使用的是 PyPy,则这些字节码是在 RPython 对象之上实现的,而 RPythondict对象又是在编译和优化的 Python 之上实现的dict。等等。

有一种间接的方法可以做到这一点,但你不会喜欢它。如果您阅读import system上的文档,您会看到搜索缓存的编译代码或调用编译器的是导入器,调用解析器的是编译器,依此类推。在 Python 3.3+ 中,这个链中的几乎所有东西都是用纯 Python 编写的,或者有一个替代的纯 Python 实现,这意味着你可以分叉代码并做你自己的事情。其中包括使用您自己的构建 AST 的 PyParsing 代码解析源代码,或将 dict 理解 AST 节点编译为您自己的自定义字节码而不是默认字节码,或对字节码进行后处理,或者……</p>

在很多情况下,一个导入钩子就足够了。如果没有,您可以随时编写自定义查找器和加载器。

如果您还没有使用 Python 3.3 或更高版本,我强烈建议您在使用这些东西之前进行迁移。在旧版本中,它更难,文档也更少,并且您最终将付出 10 倍的努力来学习在迁移时将过时的东西。

无论如何,如果您觉得这种方法很有趣,您可能想看看MacroPy。您可以从中借用一些代码——也许更重要的是,了解如何使用其中一些功能(文档中没有很好的示例)。

或者,如果您愿意满足于不那么酷的东西,您可以使用MacroPy构建一个“odict理解宏”并使用它。(请注意,MacroPy 目前仅适用于 Python 2.7,而不是 3.x。)你不能完全得到o{…},但你可以得到,比如说,od[{…}],这还不错。下载od.pyrealmain.pymain.py,然后运行python main.py以查看它是否正常工作。关键是这段代码,它采用AST,将其转换为key-value s 上DictionaryComp的等价物,并将其包装在 a to 中:GeneratorExprTupleCallcollections.OrderedDict

def od(tree, **kw):
    pair = ast.Tuple(elts=[tree.key, tree.value])
    gx = ast.GeneratorExp(elt=pair, generators=tree.generators)
    odict = ast.Attribute(value=ast.Name(id='collections'), 
                          attr='OrderedDict')
    call = ast.Call(func=odict, args=[gx], keywords=[])
    return call

当然,另一种选择是修改 Python 解释器。

我建议O{…}您在第一次尝试时放弃语法概念,而只是将普通的 dict 理解编译为 odicts。好消息是,您实际上不需要更改语法(这超出了毛茸茸的......),只需以下任何一种:

  • dictcomps 编译成的字节码,
  • 解释器运行这些字节码的方式,或者
  • PyDict类型的实现

坏消息,虽然所有这些都比更改语法容易得多,但它们都不能从扩展模块中完成。(嗯,你可以通过做与纯 Python 做的基本相同的事情来做第一个......你可以通过挂钩 .so/.dll/.dylib 来修补你自己的函数来做任何事情,但那是与在 Python 上进行黑客攻击完全相同的工作以及在运行时挂钩的额外工作。)

如果您想破解CPython 源代码,您需要的代码位于Python/compile.cPython/ceval.cObjects/dictobject.c中,开发指南会告诉您如何找到所需的一切。但是您可能想考虑改用PyPy 源代码,因为它主要是用 Python(的子集)而不是 C 编写的。


附带说明一下,即使一切都在 Python 语言级别完成,您的尝试也不会奏效。olddict, dict = dict, OrderedDict创建一个dict在模块的全局变量中命名的绑定,它会隐藏内置函数中的名称,但不会替换它。您可以替换内置函数中的东西(好吧,Python 不能保证这一点,但是对于我尝试过的每个实现/版本,都有特定于实现/版本的东西可以工作……),但是您所做的是不是这样做的方法。

于 2014-01-14T00:09:24.483 回答
16

稍微修改@Max Noel 的响应,您可以使用列表推导而不是生成器以有序的方式创建 OrderedDict(这当然不可能使用字典推导)。

>>> OrderedDict([(i, i * i) for i in range(5)])
OrderedDict([(0, 0), 
             (1, 1), 
             (2, 4), 
             (3, 9), 
             (4, 16)])
于 2015-04-28T21:16:14.557 回答