Python之数据类型

内置类型

数据类型

  • 空置:None
  • 数字:bool,int,float,long,complex
  • 序列:str,unicode,list,tuple
  • 字典:dict
  • 集合:set, frozenset

数字

bool

None,0,空字符串,空字符串,没有元素的容器类型都可以认为False反之为True

# !/usr/bin/env python3
# -*- coding: utf-8 -*-
# @Time : 2021/3/11 下午7:13
# @Author : SR
# @Email : srcoder@163.com
# @File : test.py
# @Software: PyCharm

print(list(map(bool, [None, 0, "", u"", list(), tuple(), dict(), set(), frozenset()])))

image-20210315082904971

虽然比较古怪,但是数字的确可以充当bool

# !/usr/bin/env python3
# -*- coding: utf-8 -*-
# @Time : 2021/3/11 下午7:13
# @Author : SR
# @Email : srcoder@163.com
# @File : test.py
# @Software: PyCharm

print(int(True))	# 1

print(int(False))	# 0

print(range(10)[True])	# 1

x = 5

print(range(10)[x > 3])	# 1

image-20210315083432042

int

在64位平台上,int类型是64位整数,整数是虚拟机特殊照顾对象

  • 整数需要在堆上开辟相对应的 PyIntBlock 块来缓存整数
  • 在区间为[-5,256)之内的小整数会被专门的数据进行缓存
  • PyIntBlock块的内存不会被系统回收,直到进程结束
小整数池

Python为了提高效率,使用小整数池子概念,避免整数频繁的被创建销毁影响运行速度

小整数池子对应取件范围为 [-5,256]处于该取件的整数对象都是提前被创建好的,不会被垃圾回收机制回收,在这个范围内的整数对象都使用同一个内存地址

# 使用交互式环境进行测试
a = 1

b = 1

c = 1

a is b is c

print(id(a),id(b),id(c))

image-20210315161530693

a2 = -6
a3 = -6
a2 is a3


a4 = 257
a5 = 257

a4 is a5

a5 = 256
a6 = 256
a5 is a6


image-20210315161759364

PyIntBlock块中的内存只会复用不会被删除,如果持有大量的整数对象将导致内存暴涨,且在资源被释放的时候,内存不会操作系统回收,造成了大量的内存泄露

如果使用 range函数创建一个大量的数字列表,这就需要大量的 PyIntBlock块来提供内存,且当资源被释放的时候内存不会被系统回收,如果使用 xrange函数每次迭代之后数字对象被回收,资源释放,在python2中使用 rangepython3已经使用 xrange

# pip install psutil 用来获取内存 统计数据

# !/usr/bin/env python3
# -*- coding: utf-8 -*-
# @Time : 2021/3/11 下午7:13
# @Author : SR
# @Email : srcoder@163.com
# @File : test.py
# @Software: PyCharm

import gc

import os

import psutil


def test():
    x = 0
    for i in range(10000000):
        x += i

    return x


def main():
    print(test())

    gc.collect()

    p = psutil.Process(os.getpid())

    print(p.memory_info())

if __name__ == '__main__':
    
    main()

image-20210315164948384

float

在Python中使用双精度的浮点数,不能准确表示十进制的小数值,尤其是四舍五入的 round的结果

3 / 2	# 在python2中返回整数 在python3中返回浮点数

print(3 * 0.1 == 0.3)	# false	由于计算机识别 0 1二进制对小数识别不准确

print(round(2.765, 2))

image-20210315192741055

round函数中如果遇到.5的情况下如果需要保留的小数尾部是奇数则进行保留如果尾部是偶数则进一

print(round(2.675, 2))	# 2.67


print(round(2.685, 2))	# 2.69

image-20210315193145406

如果确实需要使用精确度此时可以使用 Decimal来控制运算精度

from decimal import Decimal, ROUND_DOWN, ROUND_UP

print(float('0.1') * 3 == float('0.3'))		# False

print(Decimal('0.1') * 3 == Decimal('0.3'))		# True

print(Decimal(2.675).quantize(Decimal('0.01'), ROUND_UP))	# quantize控制位数 四舍五入 2.68

print(Decimal(2.675).quantize(Decimal('0.01'), ROUND_DOWN))	# 2.67

image-20210315200807287

字符串

与字符串相关的问题很多,比如池化,编码等,字符串为不可变类型数据,其内部存放的是字符序列或者二进制数据

  • 短字符串被存储在arena区域,str,unicode单字符串被永久缓存
  • str没有缓存机制,unicode则保留1024个宽字符长度小于9的复用对象
  • 内部包含hash值,str另外有标记判断是否被池化

基本方法

累加累乘
print("a" + "b")	# ab

print('a' * 3)	# aaa
join

使用拼接符号拼接字符串返回字符串

print('|'.join(['a', 'b', 'c']))  # a|b|c

print(''.join(['a', 'b', 'c']))   # abc

split

按照指定字符分割,返回列表

print('a|b|c'.split('|'))	# ['a', 'b', 'c']
splitlines

按照行进行分割,返回列表

print('a
b
c
'.splitlines())  # ['a', 'b', 'c']

print('a
b
c
'.splitlines(True))  # 分割后保留换行符 ['a
', 'b
', 'c
']
startswith&&endswith

判断以什么开头或者结尾,返回布尔值

print('abc'.startswith('a'), 'abc'.endswith('c'))   # True True
upper&&lower

将字符串转换成全大写或者全小写

print('Abc'.upper(), 'aBC'.lower())  # ABC abc
find
  • 查找字符串是否包含子字符串
  • 如果指定开始和结束则查找是否在指定范围内
  • 如果包含则返回开始的索引值,反之返回-1
info = 'abcd'

print(info.find('a'))  # 0

print(info.find('a', 1))  # -1

print(info.find('b', 1, 3))  # 1

print(info.find('3'))  # -1
lstrip&&rstirp&&strip

移除指定字符

print(" abc".lstrip(), "abc ".rstrip(), " abc ".strip())    # abc abc abc

print('abc'.strip('ab'))    # c
replace

对字符串进行替换

print('abcabc'.replace('bc', 'BC'))    # aBCaBC
format
指明道姓格式化
print('{key} = {value}'.format(key='x', value='100'))   # x = 100
索引格式化
print('{0},{1},{0}'.format(1, 2, ))     # 1,2,1 按照索引进行格式化
千分位符号
print('{0:,}'.format(1234567))  # 1,234,567
千分位,带小数位
print('{0:.3f}'.format(1234.5678))  # 保留三位小数    1234.568
成员
print('{.platform}'.format(sys))    # linux
字典
print('{0[a]},{1[y]}'.format(dict(a=100, b=10), dict(x=200, y=20)))  # 100,20

列表
print('{0[5]}'.format(range(10)))   # 5

编码

python2默认使用 ASCII编码在python3中默认使用 utf-8编码,在python2中为了编码转换,必须将操作系统编码与字符编码统一

import sys

import locale

print(locale.getdefaultlocale())  # 查看当前系统编码

print(sys.getdefaultencoding())  # 查看python默认编码

image-20210315215741417

strunicode都提供了encodedecode方法

  • encode将默认编码转换成其余编码
  • decode将默认或者其余编码转换成unicode编码

格式化

指名道姓
print('%(key)s=%(value)s' % dict(key='a', value=100))  # a=100
对齐
print('[%-10s]' % 'a')	# [a         ] 左对齐

print('[%10s]' % 'a')	# [         a] 右对齐
数字符号
print('%+d,%+d' % (-10, 10))	# -10,+10
填充
print('%010d' % 10)  # 0000000010共10位不够的用0填充
小数位
print('%0.2f' % 3.1415936)  # 保留两位小数
进制前缀
print('%#x,%#x' % (100, 200))   # 0x64,0xc8

池化

在Python进程中,无数的对象拥有 __name____doc__这样的名字,池化有助于减少对象数量和内存消耗,提升性能

  • 对于长度为小于5的字符串会被池化
  • 如果字符串只包含数字,字母,下划线会被池化
  • 当处于同一行不同的变量赋予相同的值,其会指向同一个对象,但是这不是池化现象属于编译器优化
# 长度都为0

a = ''

b = ''

print(id(a))    # 139698457602736

print(id(b))    # 139698457602736

print(a is b)   # True

image-20210316164109913

# 长度都为1

a = '1'

b = '1'

print(id(a))	# 140115401485256

print(id(b))	# 140115401485256

print(a is b)	# True

image-20210316164614190

# 长度都为5

a10 = 'magic'	# 140065932133520


b10 = 'magic'	# 140065932133520

print(id(a10))

print(id(b10))

print(a10 is b10)	# True

image-20210316165327153

# 长度大于5

a10 = 'magic!'	

b10 = 'magic!'	

print(id(a10))	# 139871762744128

print(id(b10))	# 139871762744240

print(a10 is b10)	# False

image-20210316165535081

# 包含数字 字母 下划线

a = "magic_string1"

b = "magic" + "_" + "string1"

print(id(a))

print(id(b))

print(a is b)

image-20210316165840003

intern

其可以把动态运行的对象进行池化,当池化的函数不在有引用的时候将会被回收

a = "".join(['a', 'b', 'c'])

s = 'abc'

print(id(s))    # 140250387396960

print(id(a))    # 140250387451440

print(s is a)  # False

image-20210316170809722

s = "".join(['a','b'])

s is "ab"

intern(s) is "ab"	# 在python2中使用intern函数

image-20210316171411998

列表

从功能上看列表类似于一个动态大小顺序容器,能够存储各种元素

  • 列表元素和存储元素的指针的数组是分开的两块内存,后者由堆分配
  • 列表会动态调整数组的大小,预分配内存多于实际元素数量

列表创建

li = []  # 使用中括号代表列表

print(type(li))  # <class 'list'>

image-20210316173205383

res = ['a', 'b'] + ['a', 'b']

print(res)  # ['a', 'b', 'a', 'b']

res1 = ['a', 'b'] * 2  # 上述累加可以更改
print(res1)  # ['a', 'b', 'a', 'b']

image-20210316173352652

res = map(bool, ['', None, 0, ()])

print(res)  # <map object at 0x7fb80c9a6e48>

print(list(res))  # 将序列或者迭代器转换成列表 [False, False, False, False]

image-20210316173641830

print([i * i for i in range(10)])   # 列表生成式 [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]	

image-20210316173749645

常见方法

数值更改
l = list('abc')

l[1] = 2

print(l)    # ['a', 2, 'c']
切片
  • 获取一定范围内的数值返回新的列表
  • 如果列表为空则返回新的空列表
l = list(range(10))

print(l[2:5])   # [2, 3, 4]

image-20210316181535843

count
  • 获取某元素在列表内的数量
  • 如果元素不存在返回0
l = list('abcdabbb')

print(l.count('b'))  # 4

print(l.count('h'))  # 0

image-20210316191807670

index
  • 获取元素在列表内的索引
  • 可以指定索引起始位置
  • 如果元素不存在则会报错
l = list('abcdabbb')

print(l.index('b'))  # 1

print(l.index('b', 3))  # 5 指定起始位置

print(l.index('h'))
sort
  • 默认将列表从小到大排序,如果加参数 reverse=True则从大到小排序
  • 如果使用变量接收排序之后的列表接收值为None
import random

l = [
    i for i in range(20)
]
random.shuffle(l)

l.sort()
print(l)  # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
random.shuffle(l)
l.sort(reverse=True)  # 从大到小
print(l)  # [19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
random.shuffle(l)
res = l.sort()
print(res)  # None

image-20210316215624245

添加元素
append
# 尾部添加元素

l = []

for i in range(5):
    
    l.append(i)
print(l)

insert
  • 如果插入的索引大于列表长度则插入末尾
  • 如果索引为负数则从后往前插入
# 在指定位置插入元素
l = list('abc')

l.insert(4, 'd')  # 末尾插入

print(l)  # ['a', 'b', 'c', 'd']

l.insert(-2, 'hh')  # 从右往左插入 ['a', 'b', 'hh', 'c', 'd']

print(l)
extend
l = list('abc')

l.extend([i for i in range(3)])

print(l)  # ['a', 'b', 'c', 0, 1, 2]

+ += extend append异同点

相同点

  • 都可以进行列表之间元素的添加
  • append与extend如果使用新的变量来接受添加之后的列表返回值为None
  • +如果使用新的变量来接受添加之后的列表会生成新的变量开辟新的内存地址

不同点

  • append原地修改可以添加任何元素,无论添加何种元素都是添加在末尾且只当一个元素
  • extend原地只可以添加可迭代对象,待添加的对象有多少个则添加多少,如果添加对象是字典则将字典的key添加列表中
  • +=原地修改列表只可以进行列表之间的操作
  • +非原地修改只能执行列表之间的操作,会拷贝生成新的对象
移除元素
clear
  • 删除列表所有元素返回一个空列表
  • 如果使用变量接收清除之后的列表获取None
  • 清空之后的列表内存地址不变
l = [i for i in range(10)]

res = l.clear()	

print(res)  # None

print(l)  # []

image-20210316211843171

del
  • 其不但能删除整个列表也可以根据索引删除列表指定值
  • 可以根据区间删除连续的元素
  • 删除之后的列表内存地址不变
l = [i for i in range(10)]

del l[0]  # [1, 2, 3, 4, 5, 6, 7, 8, 9]删除首位

print(l)

l1 = [i for i in range(10)]

del l1[1:5]  # [0, 5, 6, 7, 8, 9]删除区间内元素

print(l1)

image-20210316212555561

pop
  • 根据索引删除指定的元素
  • 如果不写索引默认删除列表最后一个元素
  • 可以使用变量接受删除的元素
  • 删除之后列表的内存地址不变
l = [i for i in range(10)]

print(id(l))

res = l.pop(1)

print(res)  # 1

print(id(l))

print(l)  # [0, 2, 3, 4, 5, 6, 7, 8, 9]

image-20210316213149843

remove
  • 删除指定的值
  • 如果值不在则会报错
  • 使用变量接受移除之后的值为None
  • 删除之后列表的内存地址不变
l = [i for i in range(10)]

print(id(l))

res = l.remove(1)

print(res)  # None

print(id(l))

print(l)  # [0, 2, 3, 4, 5, 6, 7, 8, 9]

image-20210316214724930

元组

元组看上去像列表的只读版本,但是在底层有不同之处

  • 只读对象,元组和元素指针是一次性连续分配内存的
  • 虚拟机缓存n个小于20的元组复用对象

基本方法

创建元组
a = (4)  # 少了逗号就变成普通的括号
print(type(a))  # <class 'int'>

a1 = (4,)
print(type(a1))  # <class 'tuple'>

s = tuple([i for i in range(10)])
print(s)  # (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)

s1 = (i for i in range(10))	# 元组生成式

print(s1)  # <generator object <genexpr> at 0x7f9a5c5cd830>

print(tuple(s1))  # 0, 1, 2, 3, 4, 5, 6, 7, 8, 9)

image-20210316224401547

count
  • 判断元素在元组中的数量
  • 如果元素不存在则返回0
s1 = tuple(
    'abcdabcd'
)

print(s1.count('a'))

print(s1.count('h'))

image-20210316224712512

index
  • 查找某元素所对应的索引值
  • 可以指定元素起始的查询位置
  • 如果元素不存在则报错
s1 = tuple(
    'abcdabcd'
)

print(s1.index('a'))  # 0

print(s1.index('a', 2))  # 4

print(s1.index('h'))  # 报错

image-20210316225101572

namedtuple

  • namedtuple位于 collections模块内,其可以帮助我们通过属性来访问相应的数据
  • namedtuple除了使用索引能够访问数据,更能够通过属性来访问数据
  • namedtuple中每个元素拥有自己的名字,使数据意义清晰明了
from collections import namedtuple

User = namedtuple('User', ['name', 'age'])  # 定义一个namedtuple类型的user类包含name age属性

user = User(name='SR', age=18)  # 创建一个对象

# user1 = User._make(['SR', 18])  # 也可以通过列表创建 但是需要使用_make方法

print(user)  # User(name='SR', age=18)

print(user.name)  # SR
print(user.age)  # 18

user = user._replace(age=20)  # 使用_replace修改数据
print(user)  # User(name='SR', age=20)

user_dict = user._asdict()  # 将user对象转换成dict
print(user_dict)  # OrderedDict([('name', 'SR'), ('age', 20)])

image-20210316230612298

字典

字典(dict)采用开放地址法的hash表来实现

  • 自带元素容量为8的 smalltable只有超出时才能到堆上额外分配元素表内存
  • 虚拟机缓存80个复用对象,但在堆上分配的元素表内存会被释放
  • 按需要动态调整容量,扩容或者收缩操作都将重新分配内存,重新hash
  • 删除元素不会立刻回收内存

基本操作

字典创建
d = {'name': 'SR'}

print(dict(name='SR', age=20))

print(dict((['a', 1], ['b', 2])))  # 使用两个序列类构造字典    {'a': 1, 'b': 2}

print(dict(zip('ab', range(1, 3))))  # {'a': 1, 'b': 2}

res = dict.fromkeys('abc')  # 不传值则value等于None
print(res)  # {'a': None, 'b': None, 'c': None}
res1 = dict.fromkeys('ab', 1)
print(res1)  # {'a': 1, 'b': 1}

res3 = {
    key: value for key, value in zip('abc', range(1, 4))
}
print(res3)  # {'a': 1, 'b': 2, 'c': 3}字典生成式

image-20210317155337807

in&&not in
  • 判断某key是否在字典中
  • 返回布尔值
d = {
    'a': 1,
    'b': 2
}

print('b' in d)  # True
del
  • 删除字典的key/value
  • 字典key不存在则报错
d = {
    'a': 1,
    'b': 2
}

del d['b']
print(d)  # {'a': 1}

#  报错
del d['c']
print(d)

image-20210317160504869

update
  • 有新的key的时候会直接将字典b更新到字典a中
  • 如果字典a与字典b有相同的键,会将原值给覆盖
  • 该方法没有返回值
a = {1: 2, 2: 2}
b = {1: 1, 3: 3}
a.update(b)
print(a)  # {1: 1, 2: 2, 3: 3} 原值被覆盖 新增增加

c = a.update(b)

print(c)    # None无返回值

image-20210317161557908

pop
  • 根据字典key弹出字典的value
  • 可以使用变量接收弹出的value
  • 如果key不存在则会报错
d = {
    'a': 1,
    'b': 2
}

res = d.pop('b')	

print(d)

print(res)

image-20210317162016635

popitem
  • 随机弹出字典的key/value
  • 如果使用变量接受弹出的值会获取一个元组
d1 = {
    'b': 2,
    'a': 1,

}
res = d1.popitem()

print(d1)  # {'b': 2}

print(res)  # ('a', 1)

image-20210317163009873

fromkeys
  • 用来创建一个字典第一个参数为一个可迭代对象作为字典的 key第二个对象作为字典的 value如果不传默认为空
  • 当通过一个字典来调用 fromkeys的时候如果后续使用一定需要复制给变量
d = dict.fromkeys(range(3))  # 无默认值
print(d)  # {0: None, 1: None, 2: None}

d1 = dict.fromkeys(range(3), 'hello')  # hello 作为默认值
print(d1)  # {0: 'hello', 1: 'hello', 2: 'hello'}

d2 = {}
d2.fromkeys(range(3), 'hello')
print(d2)  # 此时字典并没有key/value

d3 = d2.fromkeys(range(3), 'hello')
print(d3)  # {0: 'hello', 1: 'hello', 2: 'hello'}

image-20210317231444978

返回默认值

get
  • 如果字典key不存在且无默认值情况下返回None
  • 如果字典key不存在有默认值则返回默认值
d = {
    'a': 1,
    'b': 2
}
print(d.get('c'))  # None

print(d.get('c', 123))  # 123默认值

image-20210317165847716

setdefault
  • 如果字典的key不存在则将key添加到字典中并且将默认值设置为value且返回默认值
  • 如果字典的key存在则直接返回value
d = {
    'a': 1,
    'b': 2
}

res = d.setdefault('a', 3)  # key存在无任何操作
res1 = d.setdefault('c', 3)  # key
print(d)  # {'a': 1, 'b': 2, 'c': 3}
print(res)  # 1
print(res1)  # 3

image-20210317172627282

迭代器操作

keys
  • 获取字典的所有key
  • 返回非常规列表如果需要常规列表需要 list转换
d = {
    'a': 1,
    'b': 2
}

res = d.keys()
print(res)  # dict_keys(['a', 'b'])

print(type(res))  # <class 'dict_keys'>

print(list(res))  # ['a', 'b']

image-20210317195716165

values
  • 获取字典所有的value
  • 返回非常规列表如果需要常规列表需要 list转换
d = {
    'a': 1,
    'b': 2
}

res = d.values()
print(res)  # dict_keys(['a', 'b'])
print(type(res))  # <class 'dict_values'>
print(list(res))  # ['1', '2']

image-20210317200136353

items
  • 获取字典的key/value键值对
  • 返回非常规列表如果需要常规列表需要 list转换
  • 在列表中以元组的形式包含key/value值
d = {
    'a': 1,
    'b': 2
}

res = d.items()
print(res)  # dict_keys(['a', 'b'])
print(type(res))  # <class 'dict_items'>
print(list(res))  # [('a', 1), ('b', 2)]

image-20210317200321941

defaultdict

  • defaultdict类的初始化函数接受一个类型作为参数,当所访问的键不存在的时候,可以实例化一个值作为默认值
  • defaultdict还可以使用任何不带参数的可调用函数,到时候函数的返回值作为默认值                
from collections import defaultdict

dd = defaultdict(list)  # 实例化一个参数
print(dd)  # defaultdict(<class 'list'>, {})

dd['a'].append(1)  # key a不存在 自动创建
print(dd)  # defaultdict(<class 'list'>, {'a': [1]})


def test():
    return 0


d1 = defaultdict(test)
print(d1)  # defaultdict(<function test at 0x7fe60a6a4ea0>, {})
d1['aa']  # 自动创建key/value
print(d1)  # defaultdict(<function test at 0x7fb12c8b7ea0>, {'aa': 0})
print(d1['aa'])  # 0

image-20210317225733406

OrderedDict

  • 字典是无顺序的可以通过orderdict排序输出
d = dict()
d['a'] = 1
d['b'] = 2
d['c'] = 3
d['e'] = 4

for k, v in d.items():
    print(k, v)

image-20210317230231382

from collections import OrderedDict

od = OrderedDict()

od['a'] = 1
od['b'] = 2
od['c'] = 3
od['d'] = 4

for k, v in od.items():
    print(k, v)

for i in range(4):
    print(od.popitem())

image-20210317230440515

集合

集合用来存储无顺序不重复对象,不重复对象除了不是同一对象值也不能一样,集合只能存储可 hash对象

# 判重公式:
(a is b) or (hash(a) == hash(b) and eq(a, b))
  • 集合不是序列类型不能像列表一样进行索引取值
  • 也不能类似于字符串或者列表进行切片操作

集合生成方法

s = set('abc')

print(type(s))  # <class 'set'>

s1 = {i for i in 'abc'}

print(type(s1))  # <class 'set'>

常见方法

in&&not in
  • 判断元素是否在集合之内
  • 返回布尔值
s = set('abc')

print('b' in s)  # True

print('d' not in s)  # True
add
  • 向集合中添加元素
  • 如果使用变量接收添加之后的集合会获取None
s = set('abc')

res = s.add('d')

print(res)  # None

print(s)

image-20210318161732816

remove
  • 移除指定元素
  • 如果值不存在会报错
  • 使用变量接受移除之后的值会获得None
  • 移除之后集合的内存地址不变
s = set('abc')
print(id(s))    # 140661762360264

res = s.remove('b')
print(s)

print(id(s))    # 140661762360264
print(res)      # None
discard
  • 如果移除的元素存在就移除
  • 移除之后集合的内存地址不变
  • 使用变量接收移除之后的值会获得None
s = set('abc')
print(id(s))  # 139763619128264
res = s.discard('a')

print(s)
print(id(s))  # 139763619128264

print(res)  # None

image-20210318162428027

集合运算

超集判断
print(set("abcd") >= set("ab"))     # True

image-20210318164047580

子集判断
print(set("bc") < set("abcd"))  # True

image-20210318164145198

并集
print(set("abcd") | set("cdef"))  # {'e', 'c', 'd', 'f', 'b', 'a'}

image-20210318164541449

交集
print(set("abcd") & set("abx"))  # {'b', 'a'}

差集
print(set("abcd") - set("ab"))  # {'d', 'c'}

image-20210318164742270

对称差集
# 不会同时出现在两个集合中的

print(set("abx") ^ set("aby"))  # {'x', 'y'}

image-20210318165000506

原文地址:https://www.cnblogs.com/SR-Program/p/14555358.html