字典

PyDictObject

源文件:Include/cpython/dictobject.h

typedefstruct{
PyObject_HEAD
/*Numberofitemsinthedictionary*/
Py_ssize_tma_used;
/*Dictionaryversion:globallyunique,valuechangeeachtime
thedictionaryismodified*/
uint64_tma_version_tag;
PyDictKeysObject*ma_keys;
/*Ifma_valuesisNULL,thetableis"combined":keysandvalues
arestoredinma_keys.
Ifma_valuesisnotNULL,thetableissplit:
keysarestoredinma_keysandvaluesarestoredinma_values*/
PyDictValues*ma_values;
}PyDictObject;

能够看到运用PyObject_HEAD定长方针头部

  • ma_used:表明字典中现已运用的元素数量。
  • ma_version_tag:表明字典版本号,每当字典修正时,该值会发生变化。这个符号可用于支持并发操作,例如dict的copy()、update()等操作都能够运用ma_version_tag来查看字典是否被其他线程修正。
  • ma_keys:指向PyDictKeysObject类型的指针,表明字典的键(key)调集。
  • ma_values:指向PyDictValues类型的指针,表明字典的值(value)调集。

总之,PyDictObject结构体中的ma_keys和ma_values变量分别对应了字典方针的键和值,详细完成方式可所以“combined”(运用同一个数组存储键值对)或许“split”(分别存储键和值)。假如是结合表,那么键值对存在ma_keys里面,此刻下面的ma_values为NULL;假如是分离表,那么”键”存在ma_keys里,”value”存在ma_values里而ma_version_tag变量则用于支持并发操作,确保字典修正时的线程安全性。

所以核心隐秘应该在PyDictKeysObject

PyDictKeysObject

Include/internal/pycore_dict.h

struct_dictkeysobject{
Py_ssize_tdk_refcnt;
/*Sizeofthehashtable(dk_indices).Itmustbeapowerof2.*/
uint8_tdk_log2_size;
/*Sizeofthehashtable(dk_indices)bybytes.*/
uint8_tdk_log2_index_bytes;
/*Kindofkeys*/
uint8_tdk_kind;
/*Versionnumber--Resetto0byanymodificationtokeys*/
uint32_tdk_version;
/*Numberofusableentriesindk_entries.*/
Py_ssize_tdk_usable;
/*Numberofusedentriesindk_entries.*/
Py_ssize_tdk_nentries;
/*Actualhashtableofdk_sizeentries.Itholdsindicesindk_entries,
orDKIX_EMPTY(-1)orDKIX_DUMMY(-2).
Indicesmustbe:0<=indice<USABLE_FRACTION(dk_size).
Thesizeinbytesofanindicedependsondk_size:
-1byteifdk_size<=0xff(char*)
-2bytesifdk_size<=0xffff(int16_t*)
-4bytesifdk_size<=0xffffffff(int32_t*)
-8bytesotherwise(int64_t*)
Dynamicallysized,SIZEOF_VOID_Pisminimum.*/
chardk_indices[];/*charisrequiredtoavoidstrictaliasing.*/
/*"PyDictKeyEntryorPyDictUnicodeEntrydk_entries[USABLE_FRACTION(DK_SIZE(dk))];"arrayfollows:
seetheDK_ENTRIES()macro*/
};
  • dk_refcnt: 引证计数,用于内存办理。
  • dk_log2_size: 哈希表巨细,有必要是2的幂次方。
  • dk_log2_index_bytes: 索引存储在哈希表中需求的字节数(2的幂次方)。
  • dk_kind: 键的类型,如字符串、整数等。
  • dk_version: 结构体的版本号,当键的数量发生变化时会被重置为0。
  • dk_usable: 可用的槽位数量,即哈希表巨细的一半。
  • dk_nentries: 已运用的槽位数量。
  • dk_indices: 存储哈希值和键的索引,巨细取决于 dk_size
  • dk_entries[]: 存储键值对的数组。

一个键值对在底层对应一个PyDictKeyEntry方针

PyDictKeyEntry

typedefstruct{
/*Cachedhashcodeofme_key.*/
Py_hash_tme_hash;
PyObject*me_key;
PyObject*me_value;/*Thisfieldisonlymeaningfulforcombinedtables*/
}PyDictKeyEntry;
  • me_hash:键方针的哈希值,防止重复核算
  • me_key:键方针指针
  • me_value:值方针指针

字典初始化

底层结构大致就是这样的,咱们结合详细比如看一下

demo.py

d={}

履行python3 -m dis demo.py,输出成果

10BUILD_MAP0#创立一个空字典,并将其存储在名为d的变量中
2STORE_NAME0(d)
4LOAD_CONST0(None)#将None压入仓库
6RETURN_VALUE#回来None

经过字节码输出成果,首先会调用BUILD_MAP来创立一个字典。

在源码中找到BUILD_MAP

Python/ceval.c

TARGET(BUILD_MAP){//界说一个名为BUILD_MAP的标签(TARGET)
PyObject*map=_PyDict_FromItems(//调用CPythonAPI中的_PyDict_FromItems函数创立字典方针
&PEEK(2*oparg),2,//PEEK(2*oparg)获取操作数(oparg)指定方位处的键(key)方针,&操作符获取其地址传递给_PyDict_FromItems函数;2表明有两个参数,由于此刻只取了一对键值
&PEEK(2*oparg-1),2,//PEEK(2*oparg-1)获取操作数指定方位处的值(value)方针,&操作符获取其地址传递给_PyDict_FromItems函数;2表明有两个参数,由于此刻只取了一对键值
oparg);//oparg是操作数,表明字典元素数量,也即在栈上需求弹出多少个键值对
if(map==NULL)//假如创立字典方针失利,则跳转到error标签
gotoerror;
while(oparg--){//循环弹出栈中的键值对,直到弹出oparg个
Py_DECREF(POP());//弹出并开释键方针
Py_DECREF(POP());//弹出并开释值方针
}
PUSH(map);//将新创立的字典方针推入栈中
DISPATCH();//跳转到下一个指令履行
}

在这段代码中,经过 PEEK 操作获取操作数指定方位处的键值对方针,并将其传递给 _PyDict_FromItems 函数。然后运用 while 循环从栈中弹出刚刚运用的键值对方针,并开释它们占用的内存空间。终究将新创立的字典方针推入栈中,并跳转到下一个指令持续履行。

_PyDict_FromItems

PyObject*
_PyDict_FromItems(PyObject*const*keys,Py_ssize_tkeys_offset,
PyObject*const*values,Py_ssize_tvalues_offset,
Py_ssize_tlength)
{
boolunicode=true;
PyObject*const*ks=keys;
for(Py_ssize_ti=0;i<length;i++){
if(!PyUnicode_CheckExact(*ks)){
unicode=false;
break;
}
ks+=keys_offset;
}
PyObject*dict=dict_new_presized(length,unicode);
if(dict==NULL){
returnNULL;
}
ks=keys;
PyObject*const*vs=values;
for(Py_ssize_ti=0;i<length;i++){
PyObject*key=*ks;
PyObject*value=*vs;
if(PyDict_SetItem(dict,key,value)<0){
Py_DECREF(dict);
returnNULL;
}
ks+=keys_offset;
vs+=values_offset;
}
returndict;
}
  • 这是一个以 PyObject 指针为回来值的函数,函数名为 _PyDict_FromItems。它接受五个参数,其间:

    • keys 是一个指向 PyObject 指针数组的常量指针,表明字典的键;
    • keys_offset 是键列表中相邻两个元素之间的偏移量,也就是键的步长,一般是 1;
    • values 是同上类型和作用的常量指针,表明字典的值;
    • values_offset 是值列表中相邻两个元素之间的偏移量,也就是值的步长,一般是 1;
    • length 表明键值对的数量。
  • 界说一个布尔类型变量 unicode 并初始化为 true;

  • 声明一个 PyObject 指针常量 ks,并赋值为 keys

  • 运用 for 循环遍历键列表,假如某个键不是 PyUnicode 方针,则将 unicode 设为 false,并跳出循环;

  • 键的遍历经过指针加偏移量的方式完成,每次迭代后 ks 指向下一个键;

  • 调用 dict_new_presized 函数创立一个空字典方针,并指定预分配容量为 length;

  • 假如创立失利,则回来空指针。

  • ksvs 从头赋值为 keysvalues

  • 运用 for 循环遍历一切键值对,每次迭代获取当前键和值,并运用 PyDict_SetItem 函数将其添加到字典方针中;

  • 假如添加失利,则开释字典方针并回来空指针;

  • 迭代完毕后回来新创立的字典方针。

咱们看看创立字典的函数dict_new_presized

Objects/dictobject.c

staticPyObject*
dict_new_presized(Py_ssize_tminused,boolunicode)
{
constuint8_tlog2_max_presize=17;
constPy_ssize_tmax_presize=((Py_ssize_t)1)<<log2_max_presize;
uint8_tlog2_newsize;
PyDictKeysObject*new_keys;
if(minused<=USABLE_FRACTION(PyDict_MINSIZE)){
returnPyDict_New();
}
/*Therearenostrictguaranteethatreturneddictcancontainminused
*itemswithoutresize.Sowecreatemediumsizedictinsteadofvery
*largedictorMemoryError.
*/
if(minused>USABLE_FRACTION(max_presize)){
log2_newsize=log2_max_presize;
}
else{
log2_newsize=estimate_log2_keysize(minused);
}
new_keys=new_keys_object(log2_newsize,unicode);
if(new_keys==NULL)
returnNULL;
returnnew_dict(new_keys,NULL,0,0);
}

PyDict_MINSIZE值默以为8,能够看到键值对的数量为0时,会调用PyDict_New创立字典

PyObject*
PyDict_New(void)
{
dictkeys_incref(Py_EMPTY_KEYS);//调用dictkeys_incref函数,将empty_dict_keys方针的引证计数加一。empty_dict_keys是一个全局变量,表明一个空字典的键列表,它的值是一个指向DictKeys结构体的常量指针。
returnnew_dict(Py_EMPTY_KEYS,NULL,0,0);
}
#definePy_EMPTY_KEYS&empty_keys_struct
staticPyDictKeysObjectempty_keys_struct={
1,/*dk_refcnt*/
0,/*dk_log2_size*/
0,/*dk_log2_index_bytes*/
DICT_KEYS_UNICODE,/*dk_kind*/
1,/*dk_version*/
0,/*dk_usable(immutable)*/
0,/*dk_nentries*/
{DKIX_EMPTY,DKIX_EMPTY,DKIX_EMPTY,DKIX_EMPTY,
DKIX_EMPTY,DKIX_EMPTY,DKIX_EMPTY,DKIX_EMPTY},/*dk_indices*/
};
#defineDKIX_EMPTY(-1)

能够看到终究调用new_dict

  • 调用 new_dict 函数,创立一个新的字典方针;
  • 第一个参数是键列表,这儿传递的是 empty_dict_keys;
  • 第二个参数是值列表,这儿传递了空指针表明没有值;
  • 第三个参数是预分配容量,这儿传递 0 表明不预分配;
  • 第四个参数是是否运用同享键战略,这儿传递 0 表明不运用同享键战略;
  • 假如创立成功,则回来新创立的字典方针,不然回来空指针。

这样咱们就知道了d = {}底层是怎么创立一个空字典的,画个图

![Python dict方针_00](/Users/hhb/Documents/hexoblog/source/_posts/Python dict方针/Python dict方针_00.png)

假如创立一个有键值对的字典,又是怎样的呢?

d={'age':18}

运行得到字节码如下:

1             0 LOAD_CONST               0 ('age')
              2 LOAD_CONST               1 (18)
              4 BUILD_MAP                1
              6 STORE_NAME               0 (d)
              8 LOAD_CONST               2 (None)
             10 RETURN_VALUE

能够看到,大致分两步完成:

  1. 创立一个字典方针
  2. 在字典方针中添加键值对 'age': 18

怎么添加键值对,应该在STORE_NAME中能够找到本相

TARGET(STORE_NAME){
PyObject*name=GETITEM(names,oparg);
PyObject*v=POP();
PyObject*ns=LOCALS();
interr;
if(ns==NULL){
_PyErr_Format(tstate,PyExc_SystemError,
"nolocalsfoundwhenstoring%R",name);
Py_DECREF(v);
gotoerror;
}
if(PyDict_CheckExact(ns))
err=PyDict_SetItem(ns,name,v);
else
err=PyObject_SetItem(ns,name,v);
Py_DECREF(v);
if(err!=0)
gotoerror;
DISPATCH();
}

STORE_NAME 是一种用于在本地称号空间中存储变量的指令。

详细来看,这段代码首先从操作数栈上弹出要存储的值 v,并获取当前的本地称号空间 ns。假如本地称号空间不存在,就会引发 SystemError 异常并跳转到 error 标签,后面的代码将不会履行。

然后,代码查看称号空间类型是否是字典类型,假如是,则运用 PyDict_SetItem() 函数将称号和值存储在字典中,不然,运用 PyObject_SetItem() 函数将称号和值存储在一般的映射方针中。终究,运用 Py_DECREF() 函数开释值 v 的引证。

假如存储进程中发生了过错(例如,称号空间中的条目无法设置),则代码跳转到 error 标签处,并整理现已压入仓库的值 v。

终究,代码调用 DISPATCH() 宏,该宏实际上是一个符号,使解释器跳转到下一条指令。

总体来说,这段代码完成了 STORE_NAME 指令的根本行为,行将给定的值存储在本地称号空间中的给定称号下。

能够看出核心在PyDict_SetItem中,就是调用该函数完成添加键值对的。

Objects/dictobject.c

int
PyDict_SetItem(PyObject*op,PyObject*key,PyObject*value)
{
//......
return_PyDict_SetItem_Take2((PyDictObject*)op,key,value);
}
int
_PyDict_SetItem_Take2(PyDictObject*mp,PyObject*key,PyObject*value)
{
//......
if(mp->ma_keys==Py_EMPTY_KEYS){
returninsert_to_emptydict(mp,key,hash,value);
}
/*insertdict()handlesanyresizingthatmightbenecessary*/
returninsertdict(mp,key,hash,value);
}

能够看到字典为空时,刺进键值对调用insert_to_emptydict完成

staticint
insert_to_emptydict(PyDictObject*mp,PyObject*key,Py_hash_thash,
PyObject*value)
{
//......
PyDictKeysObject*newkeys=new_keys_object(PyDict_LOG_MINSIZE,unicode);

//......
dictkeys_decref(Py_EMPTY_KEYS);
mp->ma_keys=newkeys;
mp->ma_values=NULL;
MAINTAIN_TRACKING(mp,key,value);

size_thashpos=(size_t)hash&(PyDict_MINSIZE-1);
dictkeys_set_index(mp->ma_keys,hashpos,0);
//......
return0;
}

关键过程:

  • 将mp->ma_keys设置为指向新keys方针,并将mp->ma_values设置为NULL
  • 函数经过对哈希值和PyDict_MINSIZE-1进行与运算来核算键值对的哈希方位。然后调用dictkeys_set_index()在keys方针中设置新键值对的索引。

到这儿咱们就知道在空字典中是怎么添加一个键值对的,上面的图完善一下

![Python dict方针_01](/Users/hhb/Documents/hexoblog/source/_posts/Python dict方针/Python dict方针_01.png)

  • 键值对保存在dk_entries中,初识字典为空,会先从索引为0的方位开始保存
  • 核算哈希方位,然后将键值对在dk_entries中的索引保存在dk_indices中索引为1的方位

刺进

d={}
d['age']=18

仍是经过字节码,找到STORE_SUBSCR,接着找到PyObject_SetItem

Objects/abstract.c

int
PyObject_SetItem(PyObject*o,PyObject*key,PyObject*value)
{
if(o==NULL||key==NULL||value==NULL){
null_error();
return-1;
}
PyMappingMethods*m=Py_TYPE(o)->tp_as_mapping;
if(m&&m->mp_ass_subscript){
intres=m->mp_ass_subscript(o,key,value);
assert(_Py_CheckSlotResult(o,"__setitem__",res>=0));
returnres;
}
if(Py_TYPE(o)->tp_as_sequence){
if(_PyIndex_Check(key)){
Py_ssize_tkey_value;
key_value=PyNumber_AsSsize_t(key,PyExc_IndexError);
if(key_value==-1&&PyErr_Occurred())
return-1;
returnPySequence_SetItem(o,key_value,value);
}
elseif(Py_TYPE(o)->tp_as_sequence->sq_ass_item){
type_error("sequenceindexmustbe"
"integer,not'%.200s'",key);
return-1;
}
}
type_error("'%.200s'objectdoesnotsupportitemassignment",o);
return-1;
}

能够看到,调用了tp_as_mapping 的办法集,并调用了该办法集的 mp_ass_subscript 办法,经过源码能够知道,终究履行的函数为dict_ass_sub

staticint
dict_ass_sub(PyDictObject*mp,PyObject*v,PyObject*w)
{
if(w==NULL)
returnPyDict_DelItem((PyObject*)mp,v);
else
returnPyDict_SetItem((PyObject*)mp,v,w);
}

大致进程

  • 核算键的哈希值(hash(key))
  • 再和 mask = PyDicMinSize – 1 做与操作,核算这个元素应该刺进哈希表的方位 index = hash(key) & mask
  • 假如哈希表中此方位是空的,那么这个元素就会被刺进其间。
  • 假如此方位已被占用,Python 便会比较两个元素的哈希值和键是否持平

​ 若两者都持平,则表明这个元素现已存在,假如值不同,则更新值

​ 若两者中有一个不持平,这种状况咱们一般称为哈希抵触(hash collision),意思是两个元素的键不持平,可是哈希值持平。这种状况下,Python 便会持续寻觅表中空余的方位,直到找到方位停止。

查找

d={'age':18}
d['age']

仍是经过字节码,找到BINARY_SUBSCR,接着找到PyObject_GetItem

PyObject_SetItem类似,终究调用了map办法集的mp_subscript,终究履行的函数为dict_subscript,感兴趣的能够翻阅源码看看,大致查找进程:

  • 对key值进行哈希运算得到索引值
  • 依据索引在dk_indices中找到存储的值
  • 假如未找到报KeyError,假如找到,依据找到的值在dk_entries中找到键值对,判别已存在的key和指定的key是否持平
  • 假如持平,回来key对应的value值;假如不持平,改动战略从头映射(从头核算索引)

删去

d={'age':18}
deld['age']

和上面查找源码过程相同,经过字节码,终究找到PyDict_DelItem

源文件:Objects/dictobject.c

int
PyDict_DelItem(PyObject*op,PyObject*key)
{
Py_hash_thash;
assert(key);
if(!PyUnicode_CheckExact(key)||(hash=unicode_get_hash(key))==-1){
hash=PyObject_Hash(key);
if(hash==-1)
return-1;
}
return_PyDict_DelItem_KnownHash(op,key,hash);
}

主要是核算hash值,先查看键是否为PyUnicode方针,假如是,则运用unicode_get_hash()函数核算其哈希值。假如unicode_get_hash()回来-1,则表明无法核算哈希值,该函数回来-1。

假如键不是PyUnicode方针,或许假如unicode_get_hash()成功,则代码调用PyObject_Hash()来核算键的哈希值。假如PyObject_Hash()回来-1,则该函数回来-1

源文件:Objects/dictobject.c

int
_PyDict_DelItem_KnownHash(PyObject*op,PyObject*key,Py_hash_thash)
{
Py_ssize_tix;
PyDictObject*mp;
PyObject*old_value;
if(!PyDict_Check(op)){
PyErr_BadInternalCall();
return-1;
}
assert(key);
assert(hash!=-1);
mp=(PyDictObject*)op;
ix=_Py_dict_lookup(mp,key,hash,&old_value);
if(ix==DKIX_ERROR)
return-1;
if(ix==DKIX_EMPTY||old_value==NULL){
_PyErr_SetKeyError(key);
return-1;
}
returndelitem_common(mp,hash,ix,old_value);
}

主要运用 _Py_dict_lookup() 函数在字典中查找具有指定键和哈希值的项,并回来其索引,终究调用 delitem_common() 函数来删去该项并回来 0 表明成功

源文件:Objects/dictobject.c

staticint
delitem_common(PyDictObject*mp,Py_hash_thash,Py_ssize_tix,
PyObject*old_value)
{
PyObject*old_key;
Py_ssize_thashpos=lookdict_index(mp->ma_keys,hash,ix);
assert(hashpos>=0);
mp->ma_used--;
mp->ma_version_tag=DICT_NEXT_VERSION();
if(mp->ma_values){
assert(old_value==mp->ma_values->values[ix]);
mp->ma_values->values[ix]=NULL;
assert(ix<SHARED_KEYS_MAX_SIZE);
/*Updateorder*/
delete_index_from_values(mp->ma_values,ix);
ASSERT_CONSISTENT(mp);
}
else{
mp->ma_keys->dk_version=0;
dictkeys_set_index(mp->ma_keys,hashpos,DKIX_DUMMY);
if(DK_IS_UNICODE(mp->ma_keys)){
PyDictUnicodeEntry*ep=&DK_UNICODE_ENTRIES(mp->ma_keys)[ix];
old_key=ep->me_key;
ep->me_key=NULL;
ep->me_value=NULL;
}
else{
PyDictKeyEntry*ep=&DK_ENTRIES(mp->ma_keys)[ix];
old_key=ep->me_key;
ep->me_key=NULL;
ep->me_value=NULL;
ep->me_hash=0;
}
Py_DECREF(old_key);
}
Py_DECREF(old_value);
ASSERT_CONSISTENT(mp);
return0;
}
  • 接纳一个 PyDictObject 方针 mp、待删去元素的哈希值 hash 和在哈希表中的索引方位 ix 以及代表被删去键对应的值的 PyObject 方针 old_value。
  • 在哈希表中查找该键值对应的下标方位 hashpos,并将其断语为非负。
  • 削减字典中元素个数,并更新版本号 ma_version_tag。假如有值方针数组 ma_values,将该方位上的值方针置空,并从值方针数组中删去此元素。不然阐明该桶的键值对现已全部删去,需求从 key 数组中删去该元素,并开释键方针 old_key。
  • 终究开释旧的值方针 old_value 和旧的键方针 old_key,并回来值 0。

哈希表

哈希函数

d={['age']:18}
d#TypeError:unhashabletype:'list'

可见列表是不行哈希类型。那哪些是可哈希方针呢?

依据哈希表性质,键方针有必要满足以下两个条件,不然哈希表便不能正常作业:

  • 哈希值在方针的整个生命周期内不能改动
  • 可比较,并且比较成果持平(运用==操作符成果为True)的两个方针的哈希值有必要相同

满足这两个条件的方针就是可哈希(hashable)方针,只有可哈希方针才可作为哈希表的键。在Python的内建类型中,不行变方针都是可哈希方针(整数、浮点数、字符串、元组(元素也有必要是不行变))。

classDemo:
pass
a=Demo()
b=Demo()
hash(a)#276116571
hash(a)#276116529

能够看到自界说的方针是可哈希方针,方针哈希值是依据方针地址核算的。

当然也能够经过__hash__()魔法办法重写哈希值核算办法

classDemo:
def__hash__(self):
return123
a=Demo()
b=Demo()
hash(a)#123
hash(a)#123

可是留意这种状况,只是重写了__eq__()办法时,将变为不行哈希方针

classDemo:
def__eq__(self,other):
returnTrue
a=Demo()
hash(a)#TypeError:unhashabletype:'Demo'

为啥呢?前面提到未重写__hash__()时,哈希值是依据方针地址核算的,方针持平(运用==操作符成果为True)时哈希值是相同的,重写了__eq()__,使得两个方针地址不同,哈希值不同,但方针却持平,造成了对立。

哈希抵触

不同的键在经过哈希函数得到的哈希值相一起,会被映射到哈希表的同一个桶中。由于哈希表选用数组完成,一个桶或许存储多个键值对,因而就出现了抵触。

哈希抵触是不行防止的,由于哈希函数无法确保关于一切的输入都能生成仅有的哈希值。当哈希表中的元素越来越多时,哈希抵触的概率也会随之添加。

哈希抵触的解决办法一般有以下两种:

  1. 链接法:将哈希到同一个桶中的键值对存储在一个链表或其他数据结构中,每次查找时遍历这个数据结构。
  2. 敞开寻址法:当发生抵触时,顺次查看哈希表中下一个方位是否为空,直到找到一个空方位停止。当查找元素时,假如发现哈希表中的某个方位不是方针元素,就持续查找下一个方位,直到找到方针元素或许遍历完整个哈希表停止。

哈希进犯

哈希进犯是指进犯者试图经过修正或假造哈希值来破坏体系的完整性或欺骗体系。

例如,进犯者能够修正一个数据的内容或长度,以使其生成的哈希值与原始数据的哈希值相同,从而伪装成原始数据。这种进犯被称为哈希磕碰进犯。为了防止哈希磕碰进犯,应该选择强壮的哈希算法,并运用恰当的盐值和密钥来加强哈希算法的安全性。此外,还能够运用更杂乱的哈希函数,例如SHA-256或SHA-512等,来防止哈希进犯。

Python 在 3.3 以前, 哈希算法只依据方针自身核算哈希值。因而,只要 Python 解释器相同,方针哈希值也肯定相同。为了防止抵触,python采纳的做法是往方针身上撒把盐(salt),详细做法:解释器启动时,产生一个随机数,哈希函数运用方针和随机数核算哈希值

想要第一时间看到最新文章,能够重视公众号:郝同学的测开日记