美文网首页python交流学习
错误笔记:覆写Python中的__cmp__

错误笔记:覆写Python中的__cmp__

作者: 78c40b03ee4e | 来源:发表于2019-03-11 16:02 被阅读1次

    前言

    最近,在做一个简单的排名函数时,我不小心踩到了Python语言的一个坑里。我花了很多时间才找出具体的原因。这是值得记录的。

    具体功能要求如下:对500人进行前500名排名,每个人的分数只增加而不减少,当得分相同时,比较列表上的时间戳,得分第一的用户排名。

    这个列表之前已经做过很多,一般来说,会先抽象一个TopItem对象,保存用户的点,以及名称、UUID和其他相关信息。然后,使用列表对象top保存500个有序元素,使用dict对象缓存保存从uuid到topitem的映射。由于python对象的reference属性,添加一个额外的dict对象不会增加topitem对象的副本,但都指向同一topitem对象,所以不会有大的内存问题。

    因此,整个数据结构可能显示在下面的伪代码中:

    class TopItem(object):
        def __init__(self, uuid, name, score, t):
            self.uuid = uuid
            self.name = name
            self.score = score
            self.t = t 
    
    
    class TopStub(object):
        def __init__(self, tops):
            self.tops = copy.deepcopy(tops)
            self.cache = {}
            for item in self.tops:
                if item.uuid not in self.cache:
                    self.cache[item.uuid] = item
    

    小编推荐一个学python的学习qun 740,3222,34
    无论你是大牛还是小白,是想转行还是想入行都可以来了解一起进步一起学习!裙内有开发工具,很多干货和技术资料分享!

    当玩家播放列表时,首先根据uuid从缓存中查找topitem对象,如果找不到该对象,则直接根据分数以二进制搜索的方式插入;如果找到对象topitem,则更新其分数、名称等信息,然后将其从top列表中删除,然后插入。在二进制搜索中(因为分数只增加或不减少,所以这里有优化)。空间)。
    这个排名,计划需要很多能够处理相同的分数(比较时间戳),所以我自然会考虑重写topitem的cmp_u函数,这样你就可以直接使用<>比较topitem,写完代码后就不漂亮了。然后编写以下CMP_uuu函数:

    class TopItem(object):
    # see above
        def __cmp__(self, other):
            if self.score != other.score:
                return cmp(self.score, other.score)
            else:
                return cmp(other.t, self.t)
    
    
    class TopStub(object):
    # see above
    

    在测试期间,我做了一个测试功能。我一次随机插入n个用户数据的随机分数,QA通过了测试。没问题。

    直到它上线。

    大约两个小时后,我发现排名完全混乱,出现了“分阶段下降”的现象,用户信息在排名中反复出现。

    经过彻底的调查,发现问题出在“CMP”和“清单”上。远程的从_cmp_u的定义可以看出,如果两个顶项的得分和t相等,那么我们将判断两个顶项是相等的。名单。删除(item)删除与item相等的第一个元素。“CMP”的改写对平等判断有明显影响。也就是说,如果两个顶级项的整数和时间戳相等,那么列表中的顶级项可能是错误的。删除(项)步骤将发生,以便在排名中同时出现多个相同的用户信息。
    写一段代码来验证:

    def test():
        t1 = TopItem('uuid1', 'aaa', 80, 10) 
        t2 = TopItem('uuid2', 'bbb', 90, 11) 
        t3 = TopItem('uuid3', 'ccc', 90, 11) 
        t4 = TopItem('uuid4', 'ddd', 100, 11) 
        tops = [t4, t3, t2, t1] 
        print 'tops:', tops
        tops.remove(t2)
        print 'after remove t2:bbb'
        print 'tops:', tops
    
    output:
    tops: [(ddd,100,11), (ccc,90,11), (bbb,90,11), (aaa,80,10)]
    after remove t2:bbb
    tops: [(ddd,100,11), (bbb,90,11), (aaa,80,10)]
    

    果然没错,我们想删除t2(bbb),但是却把和t2“相等”的t3(ccc)删除了。

    那么接下来的修正也很简单:

    def __cmp__(self, other):
            if self.score != other.score:
                return cmp(self.score, other.score)
            elif self.t != other.t:
                return cmp(other.t, self.t)
            else:
                return cmp(id(self), id(other))
    
    run test again and output:
    tops: [(ddd,100,11), (ccc,90,11), (bbb,90,11), (aaa,80,10)]
    after remove t2:bbb
    tops: [(ddd,100,11), (ccc,90,11), (aaa,80,10)]
    

    总结

    记得之前看一些Python的文章时,有人说过,对于双下划线开头的这些函数,除非你确切知道自己在干什么,否则不要覆写这些函数。当时我还不以为然:既然Python提供了这么多有意思的钩子,干嘛不用呢?现在我才明白。

    另外,覆写cmp函数时,在最后一个else分支中,比较id是比较稳妥的做法。

    最后,Python3已经废弃掉cmp函数,改为提供eq, lt等多个函数,进一步降低使用者犯错的几率。

    原文来自:http://blog.guoyb.com/2017/09/02/python-cmp/
    侵删

    相关文章

      网友评论

        本文标题:错误笔记:覆写Python中的__cmp__

        本文链接:https://www.haomeiwen.com/subject/sujbpqtx.html