1.说明

元组(tuple)、列表(list)和集合(set)是Python中常见的数据结构,它们有一些重要的区别。

  1. 可变性:列表是可变的(mutable),可以对其进行添加、删除和修改操作。元组是不可变的(immutable),一旦创建就无法修改。集合也是可变的,但是它具有去重功能,不允许存在重复的元素,并且没有固定的顺序。

  2. 语法:列表使用方括号[]来表示,元素之间用逗号,分隔;元组使用圆括号()``来表示,元素之间同样用逗号,分隔;集合使用大括号{}来表示,元素之间也用逗号,`分隔。

    例如:

    1
    2
    3
    my_list = [1, 2, 3]
    my_tuple = (4, 5, 6)
    my_set = {7, 8, 9}
  3. 索引和切片:列表和元组都支持通过索引访问元素和切片操作。而集合是无序的,不支持索引和切片操作。

  4. 重复元素:列表和元组允许存在重复的元素,而集合会自动去除重复的元素。

    例如:

    1
    2
    3
    4
    my_list = [1, 1, 2, 2, 3, 3]  # 列表中可以有重复元素
    my_tuple = (4, 4, 5, 5) # 元组中也可以有重复元素
    my_set = {6, 6, 7, 7} # 集合会自动去除重复的元素
    # 如上my_set = {6,7}
  5. 可哈希性:集合的元素必须是可哈希(hashable)的,而列表和元组的元素可以是可哈希或不可哈希的。可哈希的意思是该对象的值在其生命周期中不发生改变,且能够唯一地确定一个对象。

总的来说

  • 元组适用于存储固定的、不可变的数据;
  • 列表适用于需要频繁对其中的元素进行增删改操作的情况;
  • 集合适用于需要确保元素唯一性且不关心元素的顺序的场景。

选择使用哪种数据结构取决于具体的需求和问题的特点。

2.可哈希性

2.1 概念

可哈希性(hashability)是指一个对象是否具有哈希值(hash value),并且能够保持不变。在Python中,可哈希的对象是指那些在其生命周期中不可变的对象。

哈希值是一个固定长度的整数,用于唯一标识一个对象。哈希值是通过将对象的内容转换为一个数字来计算得到的。可哈希的对象具有以下特点:

  1. 哈希值不会改变:一个对象的哈希值在其生命周期中是不变的,即使对象的内容发生了改变。
  2. 相等的对象具有相同的哈希值:如果两个对象是相等的,则它们的哈希值也相等。

可哈希性在Python中非常重要,主要体现在两个方面:

  1. 字典的键(key)必须是可哈希的对象:因为字典是基于哈希表实现的,它使用键的哈希值来索引和快速查找对应的值。所以字典中的键必须是不可变的对象,例如整数、浮点数、字符串、元组等都是可哈希的。
  2. 集合的元素必须是可哈希的对象:集合也是基于哈希表实现的,它使用哈希值来确定元素是否存在于集合中,并且保证集合中的元素唯一性。所以集合中的元素也必须是不可变的对象。

总结起来,可哈希性是指对象在其生命周期中不发生改变,并且能够通过哈希函数计算得到一个固定的、唯一的哈希值。可哈希的对象可以作为字典的键和集合的元素使用。

2.2 自定义类型的可哈希性

自定义类型(Custom types)可以维护可哈希性,但需要满足一定的条件。

在Python中,对象的可哈希性是由其所属类的__hash__()方法和__eq__()方法共同决定的。下面是关于自定义类型维护可哈希性的条件:

  1. __hash__()方法的实现:自定义类型必须定义__hash__()方法,该方法返回一个整数作为对象的哈希值。通常情况下,可使用内置函数hash()来计算哈希值,具体实现如下:

    1
    2
    def __hash__(self):
    return hash((self.attribute1, self.attribute2, ...))

    注意,__hash__()方法应该返回一个不可变的值,并且相等的对象应该具有相等的哈希值。

  2. __eq__()方法的实现:为了确保相等的对象具有相等的哈希值,自定义类型也必须定义__eq__()方法来比较两个对象是否相等。__eq__()方法通常与__hash__()方法配合使用,具体实现如下:

    1
    2
    3
    4
    def __eq__(self, other):
    if isinstance(other, self.__class__):
    return (self.attribute1 == other.attribute1) and (self.attribute2 == other.attribute2) and ...
    return False

    注意,__eq__()方法应该返回布尔值表示两个对象是否相等。

通过正确实现__hash__()__eq__()方法,自定义类型就可以维护可哈希性。这样,对象就可以作为字典的键或集合的元素,并能够保持不变性和相等性的判断。

然而,请注意,如果自定义类型中的属性是可变的(例如列表、集合等),则对象可能会发生改变,导致哈希值的改变。因此,在定义可哈希的自定义类型时,应该避免使用可变的属性

2.2.1 hash(自定义类型)

当我们使用 hash函数来处理自定义类型时,python就会调用这个自定义类型的 __hash__()函数

1
2
3
hash(p1)
# 等价于
p1.__hash__()

默认情况下,__hash__方法返回对象的标识符,而 __eq__ 方法在两个对象相同时返回 True。如果想要覆盖这个默认行为,我们可以实现__hash__ 方法和 __eq__ 方法。

2.2.2 只实现eq

如果一个自定义类型只实现了 __eq__而没有实现 __hash__,那么这个类型就不具备可哈希性,也就不能作为dict或者集和的键值

比如如下person对象只实现了 __eq__

1
2
3
4
5
6
7
class Person:
def __init__(self, name, age):
self.name = name
self.age = age

def __eq__(self, other):
return isinstance(other, Person) and self.age == other.age

如果尝试将这个对象放入set,就会报错

1
2
3
4
members = {
Person('John', 22),
Person('Jane', 22)
}
1
TypeError: unhashable type: 'Person'

同时person对象失去了hash功能

1
2
hash(Person('John', 22))
# TypeError: unhashable type: 'Person'

为了使得 Person 类可哈希,我们还需要实现 __hash__ 方法

1
2
3
4
5
6
7
8
9
10
class Person:
def __init__(self, name, age):
self.name = name
self.age = age

def __eq__(self, other):
return isinstance(other, Person) and self.age == other.age

def __hash__(self):
return hash(self.age)

现在,Person 类既支持基于 age 的等值比较,又具有哈希功能。

为了使得 Person 能够正常用于字典这种数据结构,类的哈希值必须具有不可变性。为此,我们可以将 age 定义为只读属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person:
def __init__(self, name, age):
self.name = name
self._age = age

@property
def age(self):
return self._age

def __eq__(self, other):
return isinstance(other, Person) and self.age == other.age

def __hash__(self):
return hash(self.age)

2.2.3 总结

  • 默认情况下,__hash__ 方法返回对象的 ID,__eq__方法使用 is 操作符进行比较。
  • 如果实现了 __eq__ 方法,Python 会将__hash__ 方法设置为 None,除非实现了自定义的__hash__ 方法。