2.2 原生数据类型
对于不同的数据,需要定义不同的数据类型。对于Python,以下几种数据类型不需要导入包即可支持。
2.2.1 数值
Python原生支持对整数和浮点数的四则运算(加、减、乘、除)以及求余、求幂等算术运算。算术运算符如表2-2所示。
表2-2 算术运算符
其中求幂运算符有最高优先级,其次是乘、除和求余运算符,最后是加、减运算符。我们可以使用圆括号来改变运算的优先级,就像数学里的一样(运算符和数字之间的空格并不强制要求,例如1+2也是合法的运算操作,只是添加空格会令代码更加便于阅读)。
例如:
print(1 + 2 * 3)
print((1 + 2) * 3)
输出结果为:
7
9
1.整数
在Python 3中,整数类型(int)可以处理任意大小的整数,没有最大、最小的限制。这点与其他大多数编程语言有所区别,甚至与Python 2也有区别。
0可以单独作为整数0,但是不可以作为数字前缀,例如输入01就会报错,如图2-5所示。
图2-5 输入以0开头的数值会报错
在使用整数进行浮点数除法(/)运算时,无论是否整除,得到的结果会是浮点数。而在混合类型运算中,布尔值True会被转换为1,False会被转换为0。如果有浮点数参与运算,则最后的结果也会被改为浮点数。
2.浮点数
浮点数(float)类似小数。因为使用科学记数法时,小数点的位置是可变的(例如1.23 × 103与12.3 × 102相等),所以小数也称为浮点数。Python中的浮点数对应其他语言中的双精度(double)浮点数。
在Python中浮点数除了常见的小数表达方式(例如1.23),还可以使用科学记数法的表达方式来表示,以e代替10,例如1.23e3等价于数学中的1.23×103。下面是一些合法的浮点数样例(它们都等于123.0):
123.0
1.23e2
12300e-2
输出结果为:
123.0
与整数不同,浮点数的精度是有限的(由于涉及浮点数实现规范,暂时不对浮点数的精度进行讨论),Python中浮点数的最大值约为1.79e308。整数在计算机内部一定是精确的,而浮点数可能会存在误差。
3.复数
需要注意的是,复数(complex)不支持整除(//)和求余(%)运算。
Python中用字母j代替数学中的虚数符号。下面是一些合法的复数样例:
3.14+15j
314e-2+15j
3.14+1.5e1j
输出结果为:
(3.14+15j)
在运算中需要注意的是,由于运算符的优先级问题,进行复数字面量计算时需要用圆括号标识以保证运算顺序正确。
例如:
print(1+2j * 2) # 错误
print((1+2j) * 2) # 正确
输出结果为:
(1+4j)
(2+4j)
4.布尔值
布尔(bool)值只有True和False两种,在Python中可以直接使用True和False表示布尔值。值比较的结果也是布尔值。
例如:
print(True)
print(False)
print(1 > 2)
print(3.14 < 4)
输出结果为:
True
False
False
True
布尔值在数值运算中,True会被转换成1,False会被转换成0。
例如:
print(True + 1)
print(True + True)
print(True / 2)
print(False + 1)
输出结果为:
2
2
0.5
1
除了数值运算,布尔值还可以进行逻辑运算,逻辑运算符如表2-3所示。
表2-3 逻辑运算符
2.2.2 空值
空值是一个特殊的值,表示为“None”。它不是0,也并非布尔值。
例如:
print(None)
print(None == 0)
print(None == True)
print(None == False)
输出结果为:
None
False
False
False
2.2.3 字符串
字符串(str)是Python中3种原生序列结构之一(其余两种分别是列表和元组)。由于Python 3默认对Unicode有良好支持,这使得它几乎可以处理世界上所有的语言文字和许多特殊符号(这也是它与Python 2有所区别的特点之一)。
1.统一码——Unicode
最早的计算机发明于美国,最早的编码使用单字节(8个二进制位称为一字节,单字节可以表达256个不同的数),且只编码了127个字符(英文字母、数字和部分符号),这种编码方式就是所谓的ASCII编码。但是对于非英文字符的语言来说,例如中文,单字节完全不足以表示人们平时使用的汉字(如果用单字节表示汉字,最多就只能表达256个不同的字,显然不够),所以后来我国制定了GB2312编码,用两字节表达一个汉字(可以容纳256 × 256个不同的字符)。
但是,如果世界上每种文字都制定一种编码,那么将会有上百种编码,并且设计它们时并没有考虑相互之间的兼容性,你将不能同时表达两种或两种以上不同语言的文字(比如你在网页上使用了GB2312编码,这个网页上就不能再出现韩文、日文等文字),因为那会造成编码混乱。
Unicode的诞生就是为了解决这个问题。Unicode又称统一码或万国码,它为每种语言制定了统一且唯一的编码方式,解决了不同语言之间编码的兼容性问题。
字符串是Python的一种序列类型,本质是字符序列。Python的字符串是不可变的,你不能对原始字符串进行修改,但是可以将修改后的字符串存到新的空间,并赋值给原来的变量。
注意:Unicode与后文讲到的文件字符编码方式并不完全一致。Unicode由于占用过多的字节位,存储文件时会浪费大量空间,因此一般情况下并不会使用Unicode作为存储文件的编码格式,而只在内存中使用它。
2.用引号创建字符串
Python通过使用英文的半角单引号或双引号包裹一段字符来创建字符串。不管是使用单引号还是双引号,在处理过程中都是一样的,没有任何区别。
例如:
print('hello')
print(“world”)
输出结果为:
hello
world
使用两种引号的目的是实现在字符串中表达引号而不用进行转义。例如在双引号包裹的字符串里使用单引号或者在单引号包裹的字符串里使用双引号。
例如:
print(“hello 'world'”)
print('good “morning”')
输出结果为:
hello 'world'
good “morning”
在Python中甚至可以使用连续的3个单引号或双引号创建多行字符串。多行字符串会保留代码中的换行符和空格。
例如:
morning = '''Hi!
Good 'morning'
'''
evening = “””Hello!
Good “evening”
“””
print(morning)
print(evening)
输出结果为:
Hi!
Good 'morning'
Hello!
Good “evening”
3.与数值类型互相转化
可以使用int()
、float()
将字符串转换为整数或浮点数。
例如:
print(int('32'))
print(float('32'))
输出结果为:
32
32.0
但是如果将带有小数点的字符串赋给int()
,将会报错,如图2-6所示。
图2-6 int()函数不能接收带有小数点的字符串
使用str()
可以将数值转换为字符串。
例如:
a = str(32.0)
print(a)
print(type(a))
输出结果为:
32.0
<class 'str'>
4.转义字符
可以使用转义字符\
,在字符串中表达用单字符难以表达的效果。常见的转义字符是换行符\n
。
例如:
print('hello \n world')
输出结果为:
hello
world
也可以使用转义字符来表示引号。
例如:
print('hello \'world\'')
输出结果为:
hello 'world'
5.用len()函数获取字符串长度
使用len()
函数可以获取字符串的字符长度。
例如:
print(len('hello'))
print(len('你好'))
输出结果为:
5
2
6.用in判断是否存在子字符串
可以只用in
语句判断一段字符串里是否存在特定的某个子字符串。这个子字符串可能在原字符串中出现多次,但是只要至少出现一次,in
语句就会返回True。
例如:
poem = 'You need Python'
print('nee' in poem)
print('早' in '早上要吃早餐')
输出结果为:
True
True
2.2.4 列表和元组
列表(list)和元组(tuple)是Python中除字符串之外的其余两种原生序列结构。序列结构都可以包含零个或多个元素,但与字符串不同的是,列表和元组不要求其中所包含的元素类型相同,每个元素都可以是Python中的任意对象(字符串中的元素只能是字符)。正因为内部元素可以是任意对象,所以列表和元组可以嵌套出任意深度和复杂度的数据结构。这里的任意对象甚至可以是同一个对象重复多次。
列表和元组的区别在于:列表内的元素是可变的,而元组在创建之后,内部的元素就不可变了,如图2-7所示。还记得前文介绍的对象可看作一个盒子吗?这里所说的内部元素不可变指的是元组内的盒子不再被替换或增删,但是元组内的盒子所包含的对象却不一定是不可变的,这取决于这个盒子本身的性质。
图2-7 元组内的元素不能改变
简单理解便是:列表内的元素可以被替换或增删,元组内的元素不可以被替换或增删。
1.列表
列表适合根据顺序和位置来确定某一元素。与字符串不同的是,列表内的元素可以被替换或增删。同一个对象允许在列表中出现多次。
(1)用[]创建列表。
列表内可以包含零个或多个元素,元素之间使用英文半角逗号分隔,外部用方括号包裹。
例如:
list_empty = []
list_str = ['hello', 'good', 'bye']
list_hybrid = [1, 'one', [2, 3], True]
(2)用list()将其他序列转为列表。
list
()
函数可以用于将元组和字符串转换成列表。
例如:
print(list('hello'))
print(list((1,2,3)))
输出结果为:
['h', 'e', 'l', 'l', 'o']
[1, 2, 3]
(3)用索引[index]获取元素。
可通过正向索引从列表中取出对应位置的元素,正向索引起始元素的索引值为0。
例如:
names = ['Liming', 'Lili', 'Daming']
print(names[0])
print(names[1])
输出结果为:
Liming
Lili
也可以通过负向索引从列表中取出对应位置的元素,负向索引起始元素的索引值为−1。
例如:
names = ['Liming', 'Lili', 'Daming']
print(names[-1])
print(names[-3])
输出结果为:
Daming
Liming
如果索引值超出列表范围,会产生越界错误,如图2-8和图2-9所示(这里列表长度是3,正向最大索引值是2,负向最小索引值是−3)。
图2-8 列表正向索引值越界
图2-9 列表负向索引值越界
(4)用索引[index]替换元素。
就像可以通过索引访问元素一样,可以通过索引替换列表中的元素。使用索引替换元素时,需要注意索引值不能超出列表范围,否则会报越界错误。
例如:
names = ['Liming', 'Lili', 'Daming']
print(names)
names[0] = 'David'
print(names)
输出结果为:
['Liming', 'Lili', 'Daming']
['David', 'Lili', 'Daming']
(5)用append()方法添加元素到尾部。
正如前面所说,在列表中可以任意添加元素。这里使用列表类型的append
()
方法将新元素添加到列表末尾。
例如:
names = ['Liming', 'Lili', 'Daming']
print(names)
names.append('David')
print(names)
输出结果为:
['Liming', 'Lili', 'Daming']
['Liming', 'Lili', 'Daming', 'David']
(6)用pop()方法删除元素。
列表中的元素也可以被任意删除。这里使用pop()
方法删除指定位置(或末尾)的元素。pop()
方法有一个可选的索引参数,当省略索引参数时默认删除列表最后一个元素。pop()
方法在被调用时不仅会删除指定的元素,也会将被删除的元素作为返回值返回。
例如:
names = ['Liming', 'Lili', 'Daming', 'David']
print(names)
names.pop()
print(names)
print(names.pop(0))
print(names)
输出结果为:
['Liming', 'Lili', 'Daming', 'David']
['Liming', 'Lili', 'Daming']
Liming
['Lili', 'Daming']
(7)用len()函数获取列表长度。
使用len()
函数可以获取列表元素的长度。
例如:
names = ['Liming', 'Lili', 'Daming', 'David']
print(len(names))
输出结果为:
4
(8)列表的赋值与复制。
正如前文所说,Python中为变量赋值的本质可看作将某个对象贴上写有变量名的小纸条。如果某个对象有多个小纸条可以访问,则对其中一个小纸条修改对象,也会造成另一个小纸条(变量)所指对象发生变化。毕竟它们指代的就是同一个对象,如图2-10所示。
图2-10 多个变量绑定一个列表对象
如果不注意这个问题,在使用列表的过程中就有可能会出现意想不到的结果。
例如:
names = ['Liming', 'Lili', 'Daming', 'David']
visitor = names
print(names)
print(visitor)
names.pop()
print(names)
print(visitor)
输出结果为:
['Liming', 'Lili', 'Daming', 'David']
['Liming', 'Lili', 'Daming', 'David']
['Liming', 'Lili', 'Daming']
['Liming', 'Lili', 'Daming']
为了在修改原始列表时不影响新的列表,我们可以使用copy()
方法复制原始列表,如图2-11所示。
图2-11 用copy()方法复制原始列表
例如:
names = ['Liming', 'Lili', 'Daming', 'David']
visitor = names.copy()
print(names)
print(visitor)
names.pop()
print(names)
print(visitor)
输出结果为:
['Liming', 'Lili', 'Daming', 'David']
['Liming', 'Lili', 'Daming', 'David']
['Liming', 'Lili', 'Daming']
['Liming', 'Lili', 'Daming', 'David']
(9)用in判断元素的值是否存在。
用in
可以判断某个元素的值是否存在于列表中。列表中值相同的元素可以有多个,但是只要有一个存在,in
就会返回True。
例如:
names = ['Liming', 'Lili', 'Daming', 'David']
print('Lili' in names)
print('Bob' in names)
输出结果为:
True
False
注意:这里判断的是元素的值,并不是元素对象。
2.元组
与列表相似,元组也可以包含任意Python对象并将其作为元素。但是与列表不同的是,元组一旦创建,内部元素便不能增删或替换。元组就像常量列表。
(1)用()创建元组。
元组内可以包含零个或多个元素,元素之间使用英文半角逗号分隔,外部用圆括号包裹。
与列表不同的是,当创建的元组只有一个元素时,元素后面必须带上逗号。当元素数量超过一个时,最后的逗号可以省略(为了从概念上便于记忆,也可以在每一个元素后面都带上逗号)。
例如:
tuple_empty = ()
tuple_name = ('Lili',)
tuple_score = (10, 2,) # tuple_score = (10, 2)也正确
这里要记住的是,如果元组只有一个元素,那么元素后面一定要带上逗号,否则赋值给变量的将是元素本身,而不是“装有”单一元素的元组。
例如:
tuple_wrong = ('wrong')
print(type(tuple_wrong))
tuple_right = ('right',)
print(type(tuple_right))
输出结果为:
<class 'str'>
<class 'tuple'>
(2)用tuple()将其他序列转换成元组。
使用tuple
()
函数可以将列表和字符串转换成元组。
例如:
print(tuple('hello'))
print(tuple([1,2,3]))
输出结果为:
('h', 'e', 'l', 'l', 'o')
(1, 2, 3)
(3)使用索引[index]获取元素。
与列表相同,可以通过正向索引从元组中取出对应位置的元素,正向索引起始元素的索引值为0。
例如:
names = ('Liming', 'Lili', 'Daming')
print(names[0])
print(names[1])
输出结果为:
Liming
Lili
也可以通过负向索引从元组中取出对应位置的元素,负向索引起始元素的索引值为−1。
例如:
names = ('Liming', 'Lili', 'Daming')
print(names[-1])
print(names[-3])
输出结果为:
Daming
Liming
如果索引值超出范围,也会产生越界错误。
(4)元组中的元素不可增删和替换。
与列表不同,元组一旦创建,其中的元素就会固定,即不可以增删和替换。对于元组,没有append()
和pop()
等操作元素的方法,通过索引来替换元组元素将会报错,如图2-12所示。
图2-12 元组内的元素不可变
需要注意的是,元组中的元素不可以增删和替换指的是元组内元素对象不可以增删和替换,并不代表元素对象不可以变化。忽视这一点也可能会导致难以察觉的程序错误发生。
例如:
tuple_hybrid = (1, 2, [])
print(tuple_hybrid)
print(tuple_hybrid[2])
tuple_hybrid[2].append('hello')
print(tuple_hybrid)
输出结果为:
(1, 2, [])
[]
(1, 2, ['hello'])
这个例子中,元组中的元素对象并没有发生实际变化,第三个元素自始至终都是同一个列表对象。即使列表内元素产生了变化,也不违反元组内的元素不可变的规则。
(5)用in判断元素的值是否存在。
用in
可以判断某个元素的值是否存在于元组中。元组中值相同的元素可以有多个,但是只要存在一个,就会返回True。
例如:
names = ('Liming', 'Lili', 'Daming', 'David')
print('Lili' in names)
print('Bob' in names)
输出结果为:
True
False
注意:这里判断的是元素的值,并不是元素对象。
2.2.5 集合
正如数学中的集合一样,Python中的集合(set)也不允许出现重复元素,同时Python提供了集合的基本操作:交和并。集合中的元素没有固定的顺序,任何情况下都不要依赖集合内元素的顺序。
集合适用于只需关心元素是否存在或需要进行交、并操作的场景。
1.用{}创建集合
集合内可以包含零个或多个元素,元素之间使用英文半角逗号分隔,外部用花括号({})包裹(使用花括号创建的对象还有字典,后文会详细介绍,需要注意区分)。
需要注意的是,空集不能用花括号创建(后文会详细介绍,空的花括号表示空字典),必须用set()
创建。
例如:
set_empty = set()
set_number ={1, 3, 10, 4, 5, 5}
set_word ={'hello', 'good', 'bye'}
set_hybrid ={'david', 1}
print(set_empty)
print(set_number)
print(set_word)
print(set_hybrid)
输出结果为:
set(){1, 3, 4, 5, 10}{'good', 'bye', 'hello'}{1, 'david'}
可以看到重复的元素在集合中只会保留一个。
2.用set()将序列转换成集合
set()
函数可以用于将序列对象转换成集合对象。
例如:
print(set('hello'))
print(set(['good', 'bye']))
print(set(('good', 'bye')))
输出结果为:
{'e', 'o', 'l', 'h'}{'good', 'bye'}{'good', 'bye'}
3.用in判断元素的值是否存在
用in
可以判断某个元素的值是否存在于集合中。
例如:
print('good' in{'good', 'hello'})
输出结果为:
True
注意:这里判断的是元素的值,并不是元素对象。
4.集合运算符
正如数学中的集合,在Python中如果需要对两个集合取交集或并集,就会用到交集运算符(&)或并集运算符(|),这两种运算符统称为集合运算符。
交集运算和并集运算的结果是一个新的集合。
可使用交集运算符获取两个集合的交集。
例如:
fruit_a ={'apple', 'orange', 'banana'}
fruit_b ={'grape', 'apple', 'cherry'}
fruit_c ={'grape', 'berry', 'cherry'}
print(fruit_a & fruit_b)
print(fruit_a & fruit_c)
输出结果为:
{'apple'}
set()
如果两个集合没有交集,则返回一个空集。
还可使用并集运算符获取两个集合的并集。
例如:
fruit_a ={'apple', 'orange', 'banana'}
fruit_b ={'grape', 'apple', 'cherry'}
fruit_c ={'grape', 'berry', 'cherry'}
print(fruit_a | fruit_b)
print(fruit_a | fruit_b | fruit_c)
输出结果为:
{'banana', 'apple', 'grape', 'orange', 'cherry'}{'banana', 'apple', 'berry', 'grape', 'orange', 'cherry'}
这里在同一行使用了两次并集运算,d = a | b | c
可以看作:
temp = a | b
d = temp | c
集合运算得到的集合都是新的集合。
2.2.6 字典
字典(dict)是一种储存键值对(key:value)的类型。字典中的元素由键(key)及其对应的值(value)组成。元素不通过数值索引访问,而通过值对应的键访问,所以键不可以重复。键值对在字典中是无序的,在使用过程中不要依赖字典内元素的顺序。从Python 3.8开始,键值对元素的顺序开始遵循建立时的顺序,但是为了实现兼容性此处仍然考虑为无序的,不依赖其顺序。
键通常是字符串,但也可以是Python中的其他不可变类型:整数、浮点数、布尔值、元组。字典是可变的,所以可以自由增删或替换其中的键值对。
1.用{}创建字典
用花括号对一系列用逗号隔开的键值对进行包裹即可创建字典。字典可以包含零个或多个键值对。
例如:
dict_empty ={}
dict_hours ={
'hour': 1,
'day': 24,
'week': 168
}
dict_hybrid ={
'clock': 8,
'say': 'good morning',
1: 'room'
}
print(dict_empty)
print(dict_hours)
print(dict_hybrid)
输出结果为:
{}{'hour': 1, 'day': 24, 'week': 168}{'clock': 8, 'say': 'good morning', 1: 'room'}
实际上在字典的花括号内并不要求代码强制缩进,甚至可以不换行,这里这么做只是为了增强代码的可读性。
字典中的值与前文介绍的序列一样,可以是Python中的任意类型的值,这意味着字典也可以与其他类型产生复杂的嵌套关系,读者可以根据自己的需要建立足够复杂的数据结构。
2.用键[key]获取元素
使用键可以获得字典中对应的元素的值。
例如:
dict_hours ={
'hour': 1,
'day': 24,
'week': 168
}
print(dict_hours['day'])
输出结果为:
24
但是,如果使用的键不在字典中,则会报错,如图2-13所示。
图2-13 访问字典中不存在的键会报错
避免这种错误的方法有两种。
第一种方法是用in
测试键是否存在。
例如:
print('month' in dict_hours)
输出结果为:
False
第二种方法是使用get()
方法。get
()
方法最多可以接收两个参数,第一个参数是必选的键的值,第二个参数是可选的用于键不存在时返回的默认值。如果不指定第二个参数,键不存在时默认返回None(注意,这里的None并非字符串,而是指前文介绍过的None类型)。
例如:
print(dict_hours.get('week'))
print(dict_hours.get('month'))
print(dict_hours.get('month', 'no month'))
输出结果为:
168
None
no month
3.用键[key]添加或替换元素
在字典中添加或替换元素非常方便,对指定的键进行赋值即可。如果键不存在于字典中,则新值会被加入字典。如果键已经存在于字典中,则键对应的原来的值会被新值所替换。由于字典使用键作为索引,所以不需要担心字典会出现越界错误。
例如:
dict_ages ={
'Lili': 20,
'Daming': 21
}
print(dict_ages)
dict_ages['Tom'] = 25
print(dict_ages)
dict_ages['Daming'] = 23
print(dict_ages)
输出结果为:
{'Lili': 20, 'Daming': 21}{'Lili': 20, 'Daming': 21, 'Tom': 25}{'Lili': 20, 'Daming': 23, 'Tom': 25}
4.用keys()方法获取所有键
使用keys()
方法可以获得字典中所有键组成的序列。
例如:
dict_hours ={
'hour': 1,
'day': 24,
'week': 168
}
print(dict_hours.keys())
输出结果为:
dict_keys(['hour', 'day', 'week'])
这里返回的类型是dict_keys
类型,可以用于迭代。如果需要列表类型,可以使用list()
函数进行转换。
例如:
print(list(dict_hours.keys()))
输出结果为:
['hour', 'day', 'week']
5.用values()方法获取所有值
使用values()
方法可以获得字典中所有值组成的序列。
例如:
dict_hours ={
'hour': 1,
'day': 24,
'week': 168
}
print(dict_hours.values())
print(list(dict_hours.values()))
输出结果为:
dict_values([1, 24, 168])
[1, 24, 168]
正如获取所有的键一样,values()
方法返回的是dict_values
类型,可以用于迭代。如果需要列表类型,可以使用list()
函数进行转换。
6.用update()方法更新字典
使用 update()
方法可以将当前字典中的键值对用另一个字典中的键值对进行更新。例如存在两个字典a
和b
,使用a.update(b)
可以将b
字典中的键值对更新到a
字典中。如果a
中存在对应的键则替换,不存在则新建。
例如:
dict_ages ={'Daming': 21, 'Lili':20}
print(dict_ages)
dict_ages.update({'Daming': 23, 'Tom': 25})
print(dict_ages)
输出结果为:
{'Daming': 21, 'Lili': 20}{'Daming': 23, 'Lili': 20, 'Tom': 25}
7.用pop()方法删除键值对
与列表相似,字典也可用pop()
方法删除指定键值对。pop()
方法接收键作为必选参数。
例如:
dict_ages ={'Daming': 21, 'Lili':20}
print(dict_ages)
print(dict_ages.pop('Lili'))
print(dict_ages)
输出结果为:
{'Daming': 21, 'Lili': 20}
20{'Daming': 21}
8.字典的赋值与复制
与列表在赋值与复制中遇到的问题一样,直接对字典对象的赋值并不会创建新的字典对象,对其中任何一个变量的修改都会体现在另一个变量上。具体原理可以查看列表的赋值与复制部分。
例如:
dict_ages ={'Daming': 21, 'Lili':20}
dict_new = dict_ages
print(dict_new)
dict_ages['Tom'] = 25
print(dict_new)
print(dict_ages)
输出结果为:
{'Daming': 21, 'Lili': 20}{'Daming': 21, 'Lili': 20, 'Tom': 25}{'Daming': 21, 'Lili': 20, 'Tom': 25}
这里可以使用copy()
方法复制一个新的字典对象。
例如:
dict_ages ={'Daming': 21, 'Lili':20}
dict_new = dict_ages.copy()
print(dict_new)
dict_ages['Tom'] = 25
print(dict_new)
print(dict_ages)
输出结果为:
{'Daming': 21, 'Lili': 20}{'Daming': 21, 'Lili': 20}{'Daming': 21, 'Lili': 20, 'Tom': 25}