添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

起步

众所周知,set() 是 Python 中的"天然去重因子"。对一串数据如: lyst = [1, 1, 2, 4, 4] ,我们常常 set 一下,也就是: set(lyst) ,达到去重目的。

那么,set() 是如何去重的呢?

自定义的数据结构

为了贴合实际的开发需求,我们常需要自定义数据结构。拿通用示例 Student 来说。

class Student(object):
    def __init__(self, name, age, sid):
        self.name = name
        self.age = age
        self.sid = sid

现在,我们实例两个 Student 对象,分别是 stu1 和 stu2,其名字 name,年龄 age,学号 sid 相同。现实生活中,可以认为这两个学生是同一人。

stu1 = Student("zhong", 15, 11198)
stu2 = Student("zhong", 15, 11198)
print(set([stu1, stu2]))
# 输出:{<__main__.Student object at 0x0030FE10>, <__main__.Student object at 0x0030FAD0>}

然而 set() 并不这样认为,因此没有实现去重效果。

__eq__函数

事实上,我们用比较操作符 == 会发现,Python 解释器认为 stu1 并不等于 stu2。

print(stu1 == stu2)  # 输出:False

会有上述现象,是因为程序没有按照现实需求运行。现实需求是:如果名字、年龄、学号都相同,那一定就是同一个人,因而我们需要重写魔法方法 __eq__() 。代码如下所示:

class Student(object):
    def __init__(self, name, age, sid):
        self.name = name
        self.age = age
        self.sid = sid
    def __eq__(self, other):
        return self.name == other.name and \
               self.age == other.age and \
               self.sid == other.sid
stu1 = Student("zhong", 15, 11198)
stu2 = Student("zhong", 15, 11198)
print(stu1 == stu2)  # 输出:True

现在我们是不是可以用 set 去重了呢?

print(set([stu1, stu2]))
---------------------------------
Traceback (most recent call last):
  File "xxxxxxxxx", line 18, in <module>
    print(set([stu1, stu2]))
TypeError: unhashable type: 'Student'

很遗憾,解释器报错了。它说 Student 类型的对象不能哈希。

__hash__函数

当我们没有为 Student 添加 __eq__() 函数时,set() 还不会报错,现在却说不能哈希?好在,我们可以重写 __hash__() 方法,改变原来的默认的哈希处理逻辑。

class Student(object):
    def __init__(self, name, age, sid):
        self.name = name
        self.age = age
        self.sid = sid
    def __eq__(self, other):
        return self.name == other.name and \
               self.age == other.age and \
               self.sid == other.sid
    def __hash__(self):
        return hash((self.name, self.age, self.sid))
stu1 = Student("zhong", 15, 11198)
stu2 = Student("zhong", 15, 11198)
print(set([stu1, stu2]))
# 输出:{<__main__.Student object at 0x0030FE10>}

为方便起见,这里借助了 tuple 的不可变特性,使其能够正确通过哈希处理。此时我们再用 set() 去重,发现成功了!

倘若在上述代码的基础上,试图把 eq 函数去掉,你会发现 set() 去重失效了。尽管它们的哈希结果相同。

class Student(object):
    def __init__(self, name, age, sid):
        self.name = name
        self.age = age
        self.sid = sid
    def __hash__(self):
        return hash((self.name, self.age, self.sid))
stu1 = Student("zhong", 15, 11198)
stu2 = Student("zhong", 15, 11198)
print(set([stu1, stu2]))  # 输出:{<__main__.Student object at 0x00A9FAD0>, <__main__.Student object at 0x00A9FE10>}
print(hash(stu1) == hash(stu2))  # 输出: True

去重原理

经过前面一步步推导,我们也得出了 set() 去重原理:

  1. set() 函数中会先调用对象的 __hash__() 方法,获取 hash 结果;
  2. 如果 hash 结果相同,用比较操作符 == (也就是调用函数 __eq__() )判断二者的值是否相等;
  3. 如果都相等,去重;否则,set() 认为二者不同,两个都保留到结果中。
  • Guan: 你是在哪儿看到报403的,我本地试了一下,可以正常访问,不过这个...
  • Veron: url=https://feed.hamibot.com/api...
  • Veron: 好东西,老Repo看着没更新怕用不了了,直接上你的版本,感谢:)
  • Guan: 这个很简单,你手动试一下就知道了
  • nigosim: 想请问一下,这个场景是B和网关还在同一网段,所以B仍能通过请求网...
  • youguanxinqing: scan 方法就是用来取值的
  • 第三: 但是取值却无法取啊
  • 九路: 测试
  • 396601500: 有用,帮到我了,谢谢
  • chauncey: 讲的很好,可以进一步讲一下vlan 和vxlan
  • Python 设计模式 Go MySQL vim c++ 继承 切片 指针 shell linux vscode 静态库 动态库 systemctl nginx rss 元类 类属性 私有属性 递归 汉诺塔 赏金问题 分支语句 循环语句 跳转语句 赋值语句 defer type 项目管理 函数 闭包 结构体 数组 字典 异常机制 引用 方法 接口 匿名组合 排列 组合 动态规划 编辑距离 默认传参 递归中的参数 abc 抽象基类 ORM socket编程 简易聊天系统 select IO复用 并发编程 信号通道 enum vector std 友元 虚函数 动态联编 字符串处理 sed 平均负载 sftp 预处理 宏定义 条件编译 文件包含 操作符 预定义宏 其他命令 命名空间 导包 相对导入 pidstat vmstat 中断次数 上下文切换 lychee 编译 链接 mysqld ngx_fastdfs_module 真假值 单例 去重 TypeError flutter 二分搜索 对角线问题 cpp 3n+1 缓存 mac 快捷键 终端 c centos7 docker 子域 主从配置 custom https http2 lychee-docker 创建型 django manjaro xfce databases k8s ssh log 网络 wireshark rust 多态 clickhouse trait emacs 读书 编程 range 声明语句