x = ['a','b']
。对于语句下面的操作,背后发生了什么?
x not in {None, False}
怎么解决出现“不可哈希类型:'list'”错误的问题?
我发现的解决方法是改为写成这样:
x != None and x!= False
但我感到困惑的是,从数学上讲,这两个布尔表达式是等效的。
x = ['a','b']
。x not in {None, False}
怎么解决出现“不可哈希类型:'list'”错误的问题?
我发现的解决方法是改为写成这样:
x != None and x!= False
但我感到困惑的是,从数学上讲,这两个布尔表达式是等效的。
下面是官方文档的陈述:
[Python 3]: class set([iterable]) :
返回一个新的集合或不可变集合对象,其元素来自于iterable。 集合的元素必须是可哈希的。
如果对象拥有永远不会在其生命周期内更改的哈希值(需要一个__hash__()方法),并且可以与其他对象进行比较(需要一个__eq__()方法),则该对象是可哈希的。相等的可哈希对象必须具有相同的哈希值。
...
Python所有的不可变内置对象都是可哈希的; 可变容器(如列表或字典)则不是。
[Python 3]: object.__contains__(self, item) (在锚点上方):
成员资格测试运算符(in和not in)通常是通过迭代序列实现的。但是,容器对象可以提供以下特殊方法来实现更高效的实现,而且也不要求对象是一个序列。
深入参考[GitHub]: python/cpython - (v3.5.4) cpython/Objects/setobject.c:
Line #1991:
static PyMethodDef set_methods[] = {
{"add", (PyCFunction)set_add, METH_O,
add_doc},
{"clear", (PyCFunction)set_clear, METH_NOARGS,
clear_doc},
{"__contains__",(PyCFunction)set_direct_contains, METH_O | METH_COEXIST, // @TODO - cfati: MARK THIS LINE
Line #1843:
static PyObject *
set_direct_contains(PySetObject *so, PyObject *key)
{
long result;
result = set_contains(so, key); // @TODO - cfati: MARK THIS LINE
if (result == -1)
return NULL;
return PyBool_FromLong(result);
}
Line #1823:
static int
set_contains(PySetObject *so, PyObject *key)
{
PyObject *tmpkey;
int rv;
rv = set_contains_key(so, key); // @TODO - cfati: MARK THIS LINE
if (rv == -1) {
if (!PySet_Check(key) || !PyErr_ExceptionMatches(PyExc_TypeError))
return -1;
PyErr_Clear();
tmpkey = make_new_set(&PyFrozenSet_Type, key);
if (tmpkey == NULL)
return -1;
rv = set_contains_key(so, tmpkey); // @TODO - cfati: MARK THIS LINE
Py_DECREF(tmpkey);
}
return rv;
}
Line #627:
static int
set_contains_key(PySetObject *so, PyObject *key)
{
setentry entry;
Py_hash_t hash;
if (!PyUnicode_CheckExact(key) ||
(hash = ((PyASCIIObject *) key)->hash) == -1) { // @TODO - cfati: MARK THIS LINE
hash = PyObject_Hash(key);
if (hash == -1)
return -1;
}
entry.key = key;
entry.hash = hash;
return set_contains_entry(so, &entry); // @TODO - cfati: MARK THIS LINE
}
Line #614:
static int
set_contains_entry(PySetObject *so, setentry *entry)
{
PyObject *key;
setentry *lu_entry;
lu_entry = set_lookkey(so, entry->key, entry->hash); // @TODO - cfati: MARK THIS LINE
if (lu_entry == NULL)
return -1;
key = lu_entry->key;
return key != NULL && key != dummy;
}
in
/ not in
),在所有代码路径上都要对候选成员(“包含者”)执行哈希操作,由于列表实例没有哈希功能,解释器会抛出TypeError。x = ('a', 'b')
set
中,这意味着所有不可哈希的元素不会属于您的set(因为您不能将它们放入其中)。if x.__hash__ and x in {None,False}:
x.__hash__
为 None
(其他替代方法:询问 Python 值的“可哈希性”),第二部分不会被评估。def belongs(x):
try:
return x in {None,False}
except TypeError: # unhashable type
return False
这两种方案都比使用list
或tuple
((None,False)
)更快,因为它们没有涉及线性搜索(如果测试列表中有很多元素,则是如此,而对于只有2个元素的情况不是这样)
try: [] in {} except TypeError: ...
更符合 Python 风格。 - decezecollections.Hashable
,但我怀疑这会非常慢)。 - Jean-François FabreTypeError
或者显然 x
的类型错误时,您可以中止整个操作或者不中止。 - deceze{None, False}
是一个集合。集合只能包含可哈希对象,因此只能测试可哈希对象的成员资格。列表不可哈希。
相反,您可以使用元组执行相同类型的成员资格比较。元组元素不需要是可哈希的。
x not in (None, False)
x
是不可哈希的,那么它就不会在集合里 :) - Jean-François Fabreset
和list
上进行成员测试的区别。__contains__
dunder(如果类实现了这个方法)。因此,如果我们编写>>> 1 in [1,2]
它将等同于
>>> list.__contains__([1,2],1)
>>> True
>>> [1,2] in [1,2,3]
>>> False #We get False instead of TypeError here
但是为什么上述情况不适用于集合呢?成员测试在列表和集合中的工作方式不同。实际上,列表和集合的实现方式也不同。说到集合,它们是使用{{link1:哈希表
}}实现的。这使得sets
能够执行成员测试,即在O(1)
中查找,而在list
中查找则需要O(n)
。因此,当在集合上执行in
时,__contains__
会尝试使用{{link2:__hash__
}}计算需要查找的对象的hash
。由于列表在Python中是不可哈希的,因此您会收到错误消息:TypeError: unhashable type: 'list'
。如果您对列表执行相同的操作,则不会收到任何错误消息,因为列表不会为成员测试计算哈希值。
简而言之,无法对不可哈希的对象执行集合的成员测试。一般来说,所有可变对象(list、set、dict)
都是不可哈希的。
x
is not hashable, so it cannot be in yourset
- Jean-François Fabreset()
中的[]
引发错误而不是评估为False
,这是否有一些合理性? - Patrick Haugh