自定义类是一个字典,但初始化没有字典副本?

时间:2016-08-13 21:38:02

标签: python dictionary inheritance

出于易读性的目的,我希望有一个自定义类,其行为与dict完全相同(但带有有意义的类型,而不是更通用的dict类型):

class Derivatives(dict):
    "Dictionary that represents the derivatives."

现在,有没有办法以不涉及副本的方式构建此类的新对象?天真的用法

derivs = Derivatives({var: 1})  # var is a Python object
实际上,

创建了作为参数传递的字典的副本,出于效率原因,我想避免这样做。

我试图绕过副本,但是在CPython中无法更改dict的类:

class Derivatives(dict):
    def __new__(cls, init_dict):
        init_dict.__class__ = cls  # Fails with __class__ assignment: only for heap types
        return init_dict

我希望能够为字典提供明确的类名,程序操作是构建此类字典的有效方法(而不是强制复制Python字典) 。这在Python中有效吗?

PS:用例可能是单键Derivatives的100,000次创建,其中键是变量(不是字符串,因此没有关键字初始化)。这实际上并不慢,所以“效率原因”在这里意味着更像“优雅”:理想情况下,当不需要副本时,不需要浪费时间复制。所以,在这个特殊情况下,问题更多的是关于Python在这里带来的优雅/清晰度,而不是关于运行速度。

2 个答案:

答案 0 :(得分:1)

TL; DR:除非你在C中这样做,否则不是通用的方法。

答案很长: dict类在C中实现。因此,除非您使用C,否则无法访问它的内部属性 - 最重要的是,它是内部哈希表。

在C中,您只需将表示哈希表的指针复制到对象中,而无需迭代dict(键,值)对并将它们插入到对象中。 (当然,它比这更复杂。请注意,我省略了内存管理细节。)

更长的答案:

我不确定你为什么担心效率。

Python将参数作为引用传递。除非你明确告诉它,否则很少有每个副本。

我在评论中读到你不能使用命名参数,因为键是实际的Python对象。这让我明白你担心复制dict键(也许是值)。但是,即使是字典键也不会被复制,并通过引用传递!请考虑以下代码:

class Test:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __hash__(self):
        return self.x

t = Test(1, 2)
print(t.y) # prints 2
d = {t: 1}
print(d[t]) # prints 1
keys = list(d.keys())
keys[0].y = 10
print(t.y)  # prints 10! No copying was made when inserting object into dictionary.

因此,唯一关注的问题是迭代dict并在Derivatives类中插入值。这是不可避免的,除非您能够以某种方式将类的内部哈希表设置为dict的内部哈希表。在纯python中无法做到这一点,因为dict类是用C实现的(如上所述)。

请注意,其他人建议使用生成器。这似乎也是一个好主意 - 比如你是从文件中读取衍生物还是用简单的公式生成它们。它会避免首先创建dict对象。但是,如果生成器只是list s(或任何其他可以包含一组值的数据结构)的包装器,效率将不会有明显的提高。

您最好的选择是坚持使用原始方法。生成器很棒,但它们不能有效地表示一组具有一系列价值(在您的场景中可能就是这种情况)。在C中做它也不值得。

编辑:毕竟可能值得在C中做到这一点!

我对Python C API的细节不是太大,但考虑在C中定义一个类,例如DerivativesBase(派生自dict)。您所做的只是在C中为__init__定义一个DerivativesBase函数,该函数将dict作为参数,并将哈希表指针从dict复制到您的DerivativesBase中对象。然后,在python中,您的Derivatives类派生自DerivativesBase并实现大部分功能。

答案 1 :(得分:1)

通过继承dict,你有三种构造函数参数的可能性:( baring {} literal)

class dict(**kwarg)
class dict(mapping, **kwarg)
class dict(iterable, **kwarg)

这意味着,为了实例化您的实例,必须执行以下操作之一:

  1. 将变量作为关键字D(x=1)传递,然后将其打包到中间字典中。
  2. 创建普通字典并将其作为mapping传递。

  3. 传递可重复的(键,值)对。

  4. 因此,在所有这三种情况下,您都需要创建中间对象以满足dict构造函数。

    单个对的第三个选项看起来像D(((var,1),)),为了便于阅读,我强烈推荐这个选项。

    因此,如果您希望您的类继承自字典,那么使用Derivatives({var: 1})是您最有效和最易读的选项。

    作为个人记录,如果你有成千上万的单对词典,我不确定dict设置如何是最好的,你可能只是重新考虑你班级的基础。 / p>