Python_5_Python的封装与解构-集合

  • 1. 封装与解构
    • 1.1. 封装
    • 1.2. 解构
    • 1.3. Python3 的解构
  • 2. set 类型
    • 2.1. set 的定义
      • 2.2.2. 删除元素

1. 封装与解构

  封装与解构属于 Python 语言的一种特性,它使用起来很像其他语言中的 "逗号表达式",但内部原理是不同的,在某些场景下:比如变量交换、复制时使用,显得非常优雅。

1.1. 封装

  封装顾名思义就是装箱,把多个值使用逗号分隔,组合在一起,本质上来看,其返回的是一个元组,只是省略了小括号。(一定要区别于 C 语言的逗号表达式)

In : t1 =(1,2)  # 定义一个元组

In : t2 = 1,2    # 省略括号,其内部还是会封装成元组

In : t1
Out:(1, 2)

In : t2
Out:(1, 2)

In : type(t1)
Out: tuple

In : type(t2)
Out: tuple

1.2. 解构

  解构,就是把箱子解开,在 Python 中表示从线性结构中把元素解开,并且顺序的赋值给其他变量,需要注意的是,解构时接受元素的变量,需要放在等式的左边,并且数量要和右边待解开的元素的个数一致。

In : t1
Out:(1, 2)

In : a,b = t1    # 表示把 1 赋给 a,把 2 赋给 b

In : a 
Out: 1

In : b
Out: 2

In : a,b,c = t1   # 当接受元素的变量多于解构的元素时,会提示 ValueError,反之相同
---------------------------------------------------------------------------
ValueError                                Traceback(most recent call last)
 in 
----> 1 a,b,c = t1

ValueError: not enough values to unpack(expected 3, got 2)

1.3. Python3 的解构

  了解了封装与解构,那么回想一下当我们需要进行变量交换的时候,是否可以通过封装与解构进行优化呢?当我们在其他语言中进行 a,b 变量的值的交换,我们需要一个中间变量 temp,即:temp = a ; a = b ; b = temp,在 Python 中我们可以省略它。

>>> a = 1
>>> b = 2
>>> a,b = b,a    # 等号右边使用了封装,而左边就使用了解构,这样就完成了变量的交换了,是不是很方便
>>> a
2
>>> b
1
>>> 

  为什么可以使用这种操作,是因为 Python 在进行变量赋值时,会先计算等式右边的表达式,封装起来,然后再进行解构,赋值给对应位置上的变量。

  • *号: 使用方式为: *变量名,贪婪吸收解构的元素并形成一个列表,无论能否吸收,都会返回一个列表
  • _号:表示丢弃一个变量(实际上是使用 _ 做变量名接受不想要的值,但不使用它,相当于把它丢弃,这是一个惯例,是一个不成文的约定,不是标准)
In : t = list(range(5))

In : t  
Out: [0, 1, 2, 3, 4]

In : head,*mid,tail = t

In : head     
Out: 0

In : mid
Out: [1, 2, 3]

In : tail     
Out: 4

  需要注意的是:

  1. *变量名 这种格式不能单独使用
  2. 也不能多个 *变量名 连续使用(因为从原理上看,两个 *变量名 连起来使用,会引起歧义,所以 Python 禁止了这种写法)
  3. *_ 这种格式,可以收集足够多的元素并丢弃之
# 从 [1,(2,3,4),5] 中取出 4 来
>>> _,(*_,a),_ = [1,(2,3,4),5]
>>> a
>>> 4

# 环境变量 JAVA_HOME=/usr/bin/java,返回环境变量名和路径
>>> env, path = 'JAVA_HOME=/usr/bin/java'.split('=')
>>> env, path
>>>('JAVA_HOME','/usr/bin/java')

# 或者

In : env, _, path = 'JAVA_HOME=/usr/bin/java'.partition('=')

In : env, path
Out:('JAVA_HOME', '/usr/bin/java')

  解构是 Python 提供的很好的功能,可以方便的提取复杂的数据结构的值,配合*_ 使用时,会更加便捷。

2. set 类型

  集合 set 在 Python 中是一个非常重要的 非线性结构,它使用 {} 表示,用三个词总结集合的特点就是:可变的无序的不重复。它的官方解释如下:

  • set 是一个无序的,不重复的 可 hash 对象 组成的集。
  • 常用来进行成员测试,在一个序列中去掉重复的对象,和进行数学上的计算,比如 交集(intersection)并集(union)差集(difference)对称差集(symmetric difference) 等。
  • 和其他容器类型相似,在一个无序的集合中支持 x in set,len(set),for x in set 等操作。
  • set 不会记录元素的位置以及元素加入集合的顺序,所以 set 不支持 索引切片 或者其他的类序列的操作。

  什么是可 hash 对象,可以简单的理解为可以被 hash() 计算的对象,在 Python 中,可 hash 对象是不可变类型的,比如 tuple, str, int 等等。

2.1. set 的定义

  Python 提供了两种定义一个集合的方式,set()set(iterable),他们的用法如下:

set() --> new empty set object     # 返回一个空的 set 对象
set(iterable) --> new set object   # 返回一个 set 对象,元素由 iterable 填充

例如:

In : s1 = set()

In : s2 = set(range(5))          # {0, 1, 2, 3, 4}

In : s3 = set(list(range(10)))   # 外面使用 list 进行转换,多此一举

In : s3
Out: {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

In : s4 = {}                     # 这种方式实际上是创建了一个空字典,而不是集合

In : s4        
Out: {}

In : type(s4)  
Out: dict

In : s5 = {(1,3),3,'a'}

In : s6 = {[1,2],(1,2,),1,2}       # list 属于不可 hash 对象,所以无法添加到 set 中去
---------------------------------------------------------------------------
TypeError                                 Traceback(most recent call last)
 in 
----> 1 s6={[1,2],(1,2,),1,2}

TypeError: unhashable type: 'list'

2.2. set 的基本操作

  set 是可变的类型,那就意味着,我们可以对 set 进行增加、删除、修改等操作。

2.2.1. 增加元素

  set 提供了两种定义一个集合的方式,addupdate,他们的用法如下:

s.add(elem) --> None            # 在集合 s 中添加一个元素 elem,如果元素存在,则什么都不做(去重特性)。(就地修改)

s.update(*others) --> None   # 把 *others 个可迭代可 hash 对象,和 s 进行并集,然后赋给 s。(就地修改)

例如:

In : s = {1, 2, 3}        

In : s.add('abc')       # 把字符串 'abc' 当作一个元素添加进去

In : s
Out: {1, 2, 3, 'abc'}  

In : s.add((1,2,3))     # 把元组(1,2,3) 当作一个元素添加进去

In : s
Out: {(1, 2, 3), 1, 2, 3, 'abc'}

In : s.update(range(5),'abcdef',[5,6,7,8])     # 合并多个可迭代可 hash 对象到 s 集合中来

In : s
Out: {(1, 2, 3), 0, 1, 2, 3, 4, 5, 6, 7, 8, 'a', 'abc', 'b', 'c', 'd', 'e', 'f'}

2.2.2. 删除元素

  set 提供了多种删除元素的方式,比如 remove ,pop,他们的用法如下:

s.remove(elem)  --> None   # 在集合 s 中删除一个元素,这个元素必须存在集合 s 中,否则会报 KeyError 异常

s.discard(elem) --> None   # 在集合 s 中删除一个元素,如果元素不存在集合中,那么什么也不做

s.pop()  --> item     # 在集合 s 中随便弹出一个元素,并返回元素的本身,如果集合本身为空,那么会提示 KeyError 异常

s.clear()  --> None   # 清空集合

例如:

In : s
Out: {(1, 2, 3), 0, 1, 2, 3, 4, 5, 6, 7, 8, 'a', 'abc', 'b', 'c', 'd', 'e', 'f'}

In : s.remove(0)  

In : s
Out: {(1, 2, 3), 1, 2, 3, 4, 5, 6, 7, 8, 'a', 'abc', 'b', 'c', 'd', 'e', 'f'}

In : s.remove(1000)       # 不存在集合内的元素,删除会报异常   
---------------------------------------------------------------------------
KeyError                                  Traceback(most recent call last)
 in 
----> 1 s.remove(1000)

KeyError: 1000

In : s = {(1, 2, 3), 1, 2, 3, 4, 5, 6, 7, 8, 'a', 'abc', 'b', 'c', 'd', 'e', 'f'}

In : s.pop()   
Out: 1

In : s
Out: {(1, 2, 3), 2, 3, 4, 5, 6, 7, 8, 'a', 'abc', 'b', 'c', 'd', 'e', 'f'}

In : s1 = set()

In : s1.pop()      # 空集合会报异常        
---------------------------------------------------------------------------
KeyError                                  Traceback(most recent call last)
 in 
----> 1 s1.pop()

KeyError: 'pop from an empty set'

In : s = {(1, 2, 3), 4, 5, 6, 7, 8, 'a', 'abc', 'b', 'c', 'd', 'e', 'f'}

In : s.discard(1000)     # 不会报异常   

In : s.discard(4) 

In : s
Out: {(1, 2, 3), 5, 6, 7, 8, 'a', 'abc', 'b', 'c', 'd', 'e', 'f'}

In : s.clear() 

In : s
Out: set()

2.2.3. 修改元素

  上来我们需要先想一个问题,为什么要修改 set 呢?修改的本质是什么?

  • 修改的本质其实就是找到这个元素,删除,然后再加入新的元素
  • 由于集合是非线性结构,所以无法被索引
  • 但是 set 是容器,可以被迭代

  所以 set 不能像 list 那样,通过索引修改元素,因为它无序的特性,修改其实等同于删除后再添加元素。

2.2.4. 成员判断

  我们说既然 set 是容器,那么我们就可以对容器内的元素进行判断,那么就需要使用成员判断符 innot in 了。

  • inx in s, 判断元素 x 是否是在集合 s 中,返回 bool 类型
  • not inx not in s, 判断元素 x 不在集合 s 中,返回 bool 类型
In : s = set(range(20))    

In : s
Out: {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}

In : 1 in s    
Out: True

In : 10000 not in s        
Out: True

2.3. set 和线性结构

  在 list,str 这种 线性结构 中进行 成员判断 时,因为需要遍历,线性结构的查询时间复杂度是 O(n),即随着数据规模的增大而增加耗时。
  而 set 等非线性结构,内部使用的是 hash 值作为 key,查询时只需把要判断的元素进行 hash,找到 set 中对应的门牌号,把里面的数据拽出来,看看是不是相同就可以,时间复杂度可以做到 O(1),查询时间和数据规模无关,效率很高。
  在 Python 中可 hash 对象 都属于不可变类型 ,Python 中的可 hash 对象如下:

  • 数值类型 int、float、complex
  • 布尔值 True、False
  • 字符串 String、Bytes
  • 元组 tuple
  • None

3. 集合

  简单来说,所谓的一个集合,就是将数个对象归类而分成一个或数个形态各异的大小整体。 一般来讲,集合 是具有某种特性的事物的整体,或是一些确认对象的汇集。构成集合的事物或对象称作 元素 或是 成员。集合的元素可以是任何事物,可以是人,可以是物,也可以是字母或数字等。 (此解释来自于维基百科)

  • 全集:所有元素的结合。例如实数集,所有实数组成的集合就是实数集
  • 子集 subset 和超集 superset:一个集合 A 所有的元素都在另一个集合 B 内,A 是 B 的子集,B 是 A 的超集
  • 真子集和真超集:A 是 B 的子集,且 A 不等于 B,A 就是 B 的真子集,B 就是 A 的真超集
  • 并集:多个集合合并的结果
  • 交集:多个集合的公共部分
  • 差集:集合中除去和其他集合共有的部分

  这些是小学数学基础概念。

3.1. 集合运算

  通过集合运算,我们可以方便地求出集合的差集、并集等,Python 的集合除了提供了大量的集合运算方法,还提供了不少的特殊符号用来表示集合运算。

3.2. 并集

  将集合 A 和集合 B 所有元素合并在一起,组成的集合称为集合 A 和集合 B 的 并集

union.png
s.union(*others) --> new set object   # 把多个集合和集合 s 进行合并,返回一个新的集合对象,使用 | 表示

s.update(*others) --> None            # 把 *others 个可迭代可 hash 对象,和 s 进行并集,然后赋给 s。(就地修改), 使用 |= 表示

3.3. 交集

  集合 A 和集合 B,由所有属于 A 且属于 B 的元素组成的集合称为 交集

intersection.png
s.intersection(*others) --> new set object   # 返回多个集合的交集,使用 & 表示

s.intersection_update(*others) --> None      # 获取多个集合的交集,就地进行修改,使用 &= 表示

3.4. 差集

  集合 A 和 B,由所有属于 A 且不属于 B 的元素组成的集合称为 差集

difference.png
s.difference(*others) --> new set object    # 返回集合 s 和其他多个集合的差集,使用 - 表示

s.difference_update(*others) --> None       # 返回集合 s 和其他多个集合的差集,就地进行修改,使用 -= 表示

3.5. 对称差集

  不属于集合 A 和集合 B 交集的其他元素组成的集合,数学表达式为:(A-B) U(B-A)

symmetric_differece.png
s.symmetric_difference(other) --> new set object   # 返回和另一个集合的对称差集,使用 ^ 表示

s.symmetric_difference_update(other) --> None      # 返回和另一个集合的对称差集,就地修改,使用 ^= 表示

3.6. 集合的其他运算

s.issubset(other) --> bool      # 判断当前集合是否是另一个集合的子集,使用 <= 表示

set1 < set2                      # 判断 set1 是否是 set2 的真子集

s.issuperset(other) --> bool    # 判断当前集合是否是 other 的超集,使用 >= 表示

set1 > set2                      # 判断 set1 是否是 set2 的真超集

s.isdisjoint(other) --> bool    # 判断当前集合和另一个集合有没有交集,没有交集返回 True

  以上集合运算举例说明之:

>>> s1 = set(range(1, 10))

>>> s2 = set(range(8, 15))

>>> s1, s2
({1, 2, 3, 4, 5, 6, 7, 8, 9}, {8, 9, 10, 11, 12, 13, 14})

>>> s1 | s2         # s1 和 s2 的并集
{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}

>>> s1 |= s2 ; s1   # 就地修改,结果赋值给 s1
{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}

>>> s1 = set(range(1, 10)) ; s2 = set(range(8, 15)) ; s1, s2
({1, 2, 3, 4, 5, 6, 7, 8, 9}, {8, 9, 10, 11, 12, 13, 14})

>>> s1 & s2         # s1 和 s2 的交集
{8, 9}

>>> s1 &= s2 ; s1   # 就地修改,结果赋值给 s1
{8, 9}

>>> s1 = set(range(1, 10)) ; s2 = set(range(8, 15)) ; s1, s2
({1, 2, 3, 4, 5, 6, 7, 8, 9}, {8, 9, 10, 11, 12, 13, 14})

>>> s1 - s2         # s1 和 s2 的差集,从 s1 中排除与 s2 的交集中的元素,得到所有属于 s1 但是不属于 s2 的元素组成的集合
{1, 2, 3, 4, 5, 6, 7}

>>> s1 -= s2 ; s1    # 就地修改,结果赋值给 s1
{1, 2, 3, 4, 5, 6, 7}

>>> s1 = set(range(1, 10)) ; s2 = set(range(8, 15)) ; s1, s2
({1, 2, 3, 4, 5, 6, 7, 8, 9}, {8, 9, 10, 11, 12, 13, 14})

>>> s1 ^ s2  # s1 和 s2 的对称差集,从 s1 和 s2 的并集中排除 s1 的 s2 交集
{1, 2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14}

>>> s1 ^= s2 ; s1    # 就地修改,结果赋值给 s1
{1, 2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14}

>>> s1,s2
({1, 2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14}, {8, 9, 10, 11, 12, 13, 14})

>>> s2 < s1  # 判断 s2 是否是 s1 的真子集
False

>>> s1 = set(range(6)) ; s2 = set(range(5, -1, -1)) ; s1, s2
({0, 1, 2, 3, 4, 5}, {0, 1, 2, 3, 4, 5})

>>> s1 <= s2   # 判断 s1 是否是 s2 的子集
True

>>> s1 < s2   # 判断 s1 是否是 s2 的真子集
False

>>> s2 >= s1   # 判断 s2 是否是 s1 的超集
True

>>> s2 > s1   # 判断 s2 是否是 s1 的真超集
False

>>> s1.isdisjoint(s2)   # 判断 s2 是否与 s1 没有交集,没有交集返回 True
False

你可能感兴趣的:(Python_5_Python的封装与解构-集合)