python由hashmap key引起的class两个相同值的对象比较的总结

上一篇文章由于一个模块比较适合python来开发,于是上手python,分析1kw个字符串类型key的dict查询很慢,寻找解决方案,之后引起的一些问题。在c++中需要一一去实现的自定义类型key的hash函数和重载==,原来python中都有同样的方式去完成。通过以下转载的两篇文章,能够解答之前的疑问

第一篇解答了相同值的两个对象之间的比较,之前使用class都是封装的方法,没有当数据结构来使用,或者使用了也是当变量赋值用,没比较过,所以没遇到过这个问题。

作者:Lee_Tech
来源:CSDN
原文:https://blog.csdn.net/lk7688535/article/details/80975688
版权声明:本文为博主原创文章,转载请附上博文链接!

摘要
什么是python对象的标识
python对象相等的判断
自定义python对象相等的条件
python对象的标识
python对象标识就是python对象自身的要素,python对象主要有3要素:

id:相当于对象在内存中的地址,相当于c的指针,可以用id(对象)来获取。

类型:python的基本对象有Number、String、List、Tuple、Set、Dictionary六种,当然还有对象的实例化,他们的类型就是对象的类名。可以通过type(对象)来获取。

值:对象的值,不解释- -。

对象相等的判断
python中的对象是否相等有两个层面,一个层面是是否是同一个对象,及在内存中是否共用一个内存区域,用is判断,另一个是对象的值是否相等,用==判断。

我目前用的最多的就是python对象的比较,即比较两个python对象是否相等,看个例子:

class student(object):
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex
    def __eq__(self, *args, **kwargs):
        return object.__eq__(self, *args, **kwargs)

like = student("like",25,"male")
xue = student("xue",23,"female")
dong = student("like",25,"male")

print(like is xue) #False
print(like is dong) #False
print(like == dong) #False

这里有两个student类的实例化对象,like和xue很明显是两个不同的对象,他们的不同体现在所占内存地址不同且对象的属性也不同。

like和dong虽然属性相同,但是is和==两种判断的结果也都为false,在实际情况中,我们大多都希望like和dong属性相同,就认为是同一个对象,所以我们需要重写类的eq方法:

class student(object):
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex
    def __eq__(self,other):
        return self.__dict__ == other.__dict__
print(like == dong) #True    

调用一个对象的dict方法可以用字典的形式输出其属性列表,由于两个对象的属性相同,所以==运算为True。

自定义python对象相等的条件
当然在实际情况下,可以更灵活的定义两个对象相等的条件,比如名字一样就认为相等。

class student(object):
    def __init__(self,name,age,sex):
        self.name = name
        self.age = age
        self.sex = sex
    def __eq__(self,other):
        return self.name == other.name

like = student("like",25,"male")
dong = student("like",23,"female")        

print(like == dong) #True 

实际场景
在实际应用中,有一个场景是处理对象是否在list里,不在就加入。

like = student("like",25,"male")
dong = student("like",25,"male")

list1 = []
list1.append(like)
if dong not in list1:
    list1.append(dong)
print(len(list1)) #1
#list的in操作就是通过==来判断是否在list中。

结合另一篇文章
这篇文章解答了自定义类型的key,如何以及为什么要自定义hash函数和比较函数。

作者:yueguanghaidao
来源:CSDN
原文:https://blog.csdn.net/yueguanghaidao/article/details/26162173
版权声明:本文为博主原创文章,转载请附上博文链接!

在 HashMap 中存放的一系列键值对,其中键为某个我们自定义的类型。放入 HashMap 后,我们在外部把某一个 key 的属性进行更改,然后我们再用这个 key 从 HashMap 里取出元素,这时候 HashMap 会返回什么?

如何面试者直接答“这要看自定义类型的hash值了”,我想面试官会非常满意。

联想到python中dict的实现,python中字典一般不存在这个问题,因为key的hash值默认是id值,一个对象的id是固定的。

看如下代码:
在这里插入图片描述

我们可以通过__hash__修改默认hash值,所以__hash__方法还是要看具体业务逻辑,比如我们业务任务name值一样就是同一个对象,看如下代码:

class B:
	def __init__(self,name):
		self.name=name
	def __hash__(self):
		return hash(self.name)
d={}
b1=B('skycrab')
b2=B('skycrab1')
d[b1]=1
d[b2]=2
b2.name='skycrab'
d[b2]=3
print d

我信心满满的认为name为‘skycrab‘的值会被更新为3,可事实如下:
{<main.B instance at 0x02543210>: 2, <main.B instance at 0x02543210>: 3, <main.B instance at 0x025436C0>: 1}

这让我百思不得其解,hash值明明一样,为什么会认为是不同对象,导致增加了一个。突然灵光一闪,难道key也需要做比较?打开python源码我们看看lookdict函数,

当更新字典时会去寻找合适的hashtable位置,调用的就是lookdict函数。

static dictentry *
lookdict(dictobject *mp, PyObject *key, register long hash)
{
	register size_t i;
	register size_t perturb;
	register dictentry *freeslot;
	register size_t mask = (size_t)mp->ma_mask;
	dictentry *ep0 = mp->ma_table;
	register dictentry *ep;
	register int cmp;
	PyObject *startkey;
 
	i = (size_t)hash & mask;
	ep = &ep0[i];
	if (ep->me_key == NULL || ep->me_key == key)
		return ep;
 
	if (ep->me_key == dummy)
		freeslot = ep;
	else {
		if (ep->me_hash == hash) {
			startkey = ep->me_key;
			cmp = PyObject_RichCompareBool(startkey, key, Py_EQ); //比较key的值
			if (cmp < 0)
				return NULL;
			if (ep0 == mp->ma_table && ep->me_key == startkey) {
				if (cmp > 0) //只有key相等才会返回已有的位置,否则会寻找一个新的位置
					return ep;
			}
			else {
				/* The compare did major nasty stuff to the
				 * dict:  start over.
				 * XXX A clever adversary could prevent this
				 * XXX from terminating.
 				 */
 				return lookdict(mp, key, hash);
 			}
		}
		freeslot = NULL;
	}

上面是lookdict的部分源码(最后没有大括号),如上代码注释,原来只有hash值一样且key值相等才有更新。那么这就好办了,定义__eq__方法即可:

class B:
	def __init__(self,name):
		self.name=name
	def __hash__(self):
		return hash(self.name)
	def __eq__(self,r):
		if self.name == r.name:
			return True
		else:
			return False
d={}
b1=B('skycrab')
b2=B('skycrab1')
d[b1]=1
d[b2]=2
b2.name='skycrab'
d[b2]=3
print d

这下结果终于符合期望了,{<main.B instance at 0x025F2620>: 2, <main.B instance at 0x025F25F8>: 3}

这里我们扩展一下,python中的dict默认采用hash_map的存储结构,所以查找效率很高,但hash_map的查找效率不稳定。

hash_map的时间复杂度在O(1)-O(N),而基于树结构的map时间复杂度O(logN),比较稳定。

所以在C++中使用hash_map还是map是有考究的,具体可以看看【C++对话系列-产生真正的hash对象】一节。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值