如何使用任何值作为字典键?

时间:2017-09-02 19:55:30

标签: python dictionary hash

我想在单个dict中使用任何类型的实例作为密钥。

def add_to_dict(my_object, d, arbitrary_val = '123'):
    d[ id(my_object) ] = arbitrary_val

d = {}
add_to_dict('my_str', arbitrary_val)
add_to_dict(my_list, arbitrary_val)
add_to_dict(my_int, arbirtray_val)

my_object = myclass()
my_object.__hash__ = None
add_to_dict(my_object, arbitrary_val)

以上不会起作用,因为my_listmy_object无法进行哈希处理。

我的第一个想法是使用id函数传递对象的id()值。

def add_to_dict(my_object, d, arbitrary_val = '123'):
    d[ id(my_object) ] = arbitrary_val

然而,由于id('some string') == id('some string')不能保证始终为True,因此无法正常工作。

我的第二个想法是测试对象是否具有__hash__属性。如果是,请使用该对象,否则,请使用id()值。

def add_to_dict(my_object, d, arbitrary_val = '123'):
    d[ my_object if my_object.__hash__ else id(my_object) ] = arbitrary_val

但是,由于hash()id()都返回int,我相信我最终会发生冲突。

如何在上面写add_to_dict(obj, d)以确保无论obj是什么listintstrobjectdict),它会正确设置字典中的项目而不会发生冲突吗?

2 个答案:

答案 0 :(得分:1)

我们可以创建一些允许我们插入可变对象的字典:

class DictionaryMutable:

    nullobject = object()

    def __init__(self):
        self._inner_dic = {}
        self._inner_list = []

    def __getitem__(self, name):
        try:
            return self._inner_dic[name]
        except TypeError:
            for key, val in self._inner_list:
                if name == key:
                    return val
            raise KeyError(name)

    def __setitem__(self, name, value):
        try:
            self._inner_dic[name] = value
        except TypeError:
            for elm in self._inner_list:
                if name == elm[0]:
                    elm[1] = value
                    break
            else:
                self._inner_list.append([name,value])

    # ...

其工作原理如下:DictionaryMutable由字典和列表组成。该字典包含可散列的不可变键,该列表包含子列表,其中每个子列表包含两个元素:键和值。

对于每次查找,我们首先尝试对字典执行查找,如果密钥name不可删除,则会抛出TypeError。在这种情况下,我们遍历列表,检查其中一个键是否匹配,如果匹配则返回相应的值。如果不存在此类元素,我们会引发KeyError

设置元素的工作方式大致相同:首先我们尝试在字典中设置元素。如果事实证明密钥是不可用的,我们在列表中线性搜索并且旨在添加元素。如果失败,我们将其添加到列表的末尾。

这种实现有一些主要的缺点:

  • 如果字典查找由于密钥不可用而失败,我们将执行线性查找,这会显着减慢查找速度;和
  • 如果更改字典中的对象,则会更新密钥,因此搜索该对象将失败。因此,它可能导致一些不可预测的行为。

这只是一个基本的实现。例如,__iter__等也需要实施。

答案 1 :(得分:1)

您可以使用对象pickle.dumps()的pickle字节流表示而不是对象的id()pickle适用于大多数内置类型,并且有一些方法可以扩展它以使用大多数值,它不知道如何自动执行。

注意:我使用对象的repr()作为其“任意值”,以便更容易在显示的输出中识别它们。

try:
    import cpickle as pickle
except ModuleNotFoundError:
    import pickle
from pprint import pprint

def add_to_dict(d, obj, arbitrary_val='123'):
    d[pickle.dumps(obj)] = arbitrary_val

class MyClass: pass

my_string = 'spam'
my_list = [13, 'a']
my_int = 42
my_instance = MyClass()

d = {}
add_to_dict(d, my_string, repr(my_string))
add_to_dict(d, my_list, repr(my_list))
add_to_dict(d, my_int, repr(my_int))
add_to_dict(d, my_instance, repr(my_instance))

pprint(d)

输出:

{b'\x80\x03K*.': '42',
 b'\x80\x03X\x04\x00\x00\x00spamq\x00.': "'spam'",
 b'\x80\x03]q\x00(K\rX\x01\x00\x00\x00aq\x01e.': "[13, 'a']",
 b'\x80\x03c__main__\nMyClass\nq\x00)\x81q\x01.': '<__main__.MyClass object at '
                                                  '0x021C1630>'}