@
**
第三章 组合数据类型
**
3.1 序列类型
3.1.1 元组
元组和字符串类似,是固定的,不能替换或者删改包含的任意项
(1, 2, 3) + (1, 2, 3) #(1, 2, 3, 1, 2, 3)
(1, 2, 3, 4) * 2 #(1, 2, 3, 4, 1, 2, 3, 4)
(1, 2, 3, 4)[:3] #(1, 2, 3)
(1, 2, 3, 1, 1, 3).count(1) #3
(1, 2, 3, 1, 1, 3).index(3) #2
hair = 'black', 'brown', 'blonde', 'red'
hair[2]
hair[-3:]
hair[:2], 'gray', hair[2:] #(('black', 'brown'), 'gray', ('blonde', 'red'))
hair[:2] + ('gray',) + hair[2:] #('black', 'brown', 'gray', 'blonde', 'red')
#请注意上面一元组的写法,别弄错了
things = (1, -7.5, ('pea', (5, 'XyZ'), 'queue'))
things[2][1][1][2] # 'Z'
3.1.2 命名的元组 (collections.nametuple())
collections 模块提供了 namedtuple() 函数,该函数用于创建自定义的元组数据类型。
import collections
Sale = collections.namedtuple('Sale', 'productid customerid data quantity price')
sales = []
sales.append(Sale(432, 932, '2008-9-14', 3, 7.99))
sales.append(Sale(419, 874, '2008-9-15', 1, 18.49))
total = 0
for sale in sales:
total += sale.quantity * sale.price
print('Total ${0:.2f}'.format(total)) #Total $42.46
第二个例子:
Aircraft = collections.namedtuple('Aircraft',
'manufacturer model seating')
Seating = collecttions.namedtuple('Seating', 'minimum maximum')
aircraft = Aircraft('Airbus', 'A320-200', Seating(100, 220))
aircraft.seating.maximum #220
print('{0} {1}'.format(aircraft.manufacturer, aircraft.model))
print('{0.manufacturer} {0.model}'.format(aircraft))
#命名的元组还有几个私有方法,有一个namedtuple._asdict()的方法特别有用
print('{manufacturer} {model}'.format(**aircraft._asdict()))
#Airbus A320-200
3.1.3 列表 (查询有关函数点这)
列表方法:
L.append(x)
L.count(x)
L.extend(m) | L += m
L.index(x, start, end)
L.insert(i, x) #在索引i的位置上插入x
a = [1, 2, 3]
a[1:1] = [4] # == a.insert(1, 4)
a # [1, 4, 2, 3]
L.pop() #返回并移除list L最右边的数据项
L.pop(i) # 索引为i的
L.remove(x) #从list中移除最左边出现的数据项x,如果找不到产生ValueError
#a[2:4] = [] | del a[2:4] euqal
L.reverse()
L.sort(...) #排序 接受可选的key和reverse参数
first, *rest = [9, 2, -4, 8, 7]
first, *mid, last = 'Charles Philip Arthur George Windsor.'.split()
*directories, executable = '/usr/local/bin/gvim'.split('/')
#(['', 'usr', 'local', 'bin'], 'gvim')
3.1.4 列表内涵
[expression for item in iterable if condition]
[y for y in range(1900, 1940) if (y % 4 == 0 and y % 100 != 0) or (y % 400 == 0)]
#[1904, 1908, 1912, 1916, 1920, 1924, 1928, 1932, 1936]
[s + z + c for s in 'MF' for z in 'SMLX' for c in 'BGW' if not (s == 'F' and z == 'X')]
第二行代码,和一般的for循环似乎不同,条件的判断似乎必须在循环的最后关头才会进行判断,所以其实for循环的顺序发生颠倒也没有什么关系。
3.2 集合类型
只有可哈希运算的对象可以添加到集合中。
为什么需要哈希?
3.2.1 集合(查询有关函数点这)
集合是0个或多个对象引用的无序组合,且排他!
{1, 1, 2, 3, 4, 5} #{1, 2, 3, 4, 5}
空集合只能用set()来创建, '{}' 用来创建空dict
语法 | 描述 |
---|---|
s.add(x) | 将数据项x添加到集合s中——如果s中尚未包含x |
s.clear() | 移除集合s中的所有数据项 |
s.copy() | 返回集合s的浅拷贝* |
s.difference(t) s-t | 返回一个新集合, 其中包含在s中但不在集合t中的所有数据项* |
s.difference_update(t) s -= t | 移除每一个在集合t但不在集合s中的项 |
s.discard(x) | 如果数据项x存在于集合s中,就移除该数据项,参见set.remove() |
s.intersection(t) s & t | 返回一个新集合,其中包含所有同时包含在集合t与s中的数据项* |
s.intersection(t) s &= t | 使得集合s包含自身与集合t交集 |
s.isdisjoint(t) | 如果集合s与t没有相同的项, 就返回True* |
s.issubset(t) s<=t | 如果集合s与集合t相同, 或者是t的子集,就返回True。 使用s<t可以测试s是否是t的真子集* |
s.issuperset(t) s >= t | 如果集合s与集合t相同,或者是t的超集,就返回True。使用s>t可以测试s是否是t的真子集* |
s.pop() | 返回并移除集合s中一个随即项,如果s为空集,就产生KeyError异常 |
s.remove(x) | 从集合s中移除数据项x,如果s中不包含x,就产生KeyError异常,参见set.discard() |
s.symmetric_difference(t) s^t | 返回一个新集合,其中包含s与t中的每个数据项,但不包含同时在这俩个集合中的数据项* |
s.symmetric_difference_update(t) s ^= t | 使得集合s只包含其自身与集合t的对称差 |
s.union(t) s | t | 返回一个新集合,其中包含集合s中的所有数据项,以及在t中而不在s中的数据项* |
s.update(t) s|= t | 将集合t中每个s中不包含的数据项添加到集合s中 |
* 这一方法及其操作符也可用于frozensets
3.2.2 集合内涵
{expression for item in iterable}
{expression for item in iterable if condition}
html = {x for x in files if x.lower().endswith(('.htm', '.html'))}
固定集合(forzeonset)
如果讲二元运算符应用于集合于固定集合,那么产生结果的数据类型与左边操作数的数据类型一致。
3.3 映射类型
只有可哈希运算的对象才能作为字典的键。
3.3.1 字典 (查询有关函数点这)
dict() | {}
d1 = dict({'id': 1948, 'name': 'Washer', 'size': 3})
d2 = dict(id = 1948, name = 'Washer', size = 3)
d3 = dict([('id', 1948), ('name', 'Washer'), ('size', 3)])
d4 = dict(zip(('id', 'name', 'size'), (1948, 'Washer', 3)))
d5 = {'id': 1948, 'name': 'Washer', 'size': 3}
#{'id': 1948, 'name': 'Washer', 'size': 3}
del d1['id'] #删除'd1'
d1.pop('id') #删除'd1'
del : del删除的不是数据,而是删除对象与数据之间的绑定,若数据没有被其他对象引用,则进入垃圾收集流程。
语法 | 描述 |
---|---|
d.clear() | 从dict d 中移除所有项 |
d.copy() | 返回dict d的浅拷贝 |
d.fromkeys(s, v) | 返回一个dict,该字典的键为序列s中的项,值为None或v |
d.get(k) | 返回键k相关联的值,如果k不在dict d中就返回None |
d.get(k, v) | 返回键k相关联的值,如果k不在dict d中就返回v |
d.items() | 返回dict d 中所有(key, value)对的视图 |
d.keys() | 返回dict d 中所有键的视图 |
d.pop(k) | 返回键k相关联的值,并移除键为k的项,如果k不包含在d中,就产生KeyError异常 |
d.popitem() | 返回并移除dict d 中任意一个(key, value)对,如果d为空就产生KeyError异常 |
d.setdefault(k, v) | 与dict.get()方法一样,不同之处在于,如果k没有包含在dict d中就插入一个键为k的新项,其值为None或v |
d.update(a) | 将a中每个尚未包含在dict d中的(key, value) 对添加到d,对同时包含在d与a中的每个键,使用a中对应的值替换d中对应的值——a可以是字典,也可以是(key,value)对的一个iterable,或关键字参数 |
d.values() | 返回dict d中所有值的视图 |
视图是一个只读的iterable对象
特别的是:如果视图引用的字典发生变化,那么视图将反映该变换;键视图与项视图支持一些类似于集合的操作。
& | - ^
d1 = dict(a = 1, b = 2, c = 3)
d2 = dict(x = 1, y = 2, z = 3)
for item in d1.items():
print(item)
d1['a'] = 7
for item in d1.items():
print(item)
#('a', 1)
#('b', 2)
#('c', 3)
#('a', 7)
#('b', 2)
#('c', 3)
d1 = dict(a = 1, b = 2, c = 3)
d2 = dict(x = 1, y = 2, z = 3)
v = d1.items()
x = d2.items()
for item in v | x:
print(item)
#('b', 2)
#('y', 2)
#('z', 3)
#('c', 3)
#('x', 1)
#('a', 1)
d = {}.fromkeys('ABCD', 3) #{'A':3, 'B':3, 'C':3, 'D':4}
s = set('ACX') # {'A', 'C', 'X'}
matches = d.keys() & s # {'A', 'C'}
一个例子(没玩过)
import string
import sys
words = {}
strip = string.whitespace + string.punctuation + string.digits + "\"'"
for filename in sys.argv[1:]: #这个是指在命令行输入的东东
for line in open(filename):
for word in line.lower().split():
word = word.strip(strip)
if len(word) > 2:
if words[word] = words.get(word, 0) + 1
for word in sorted(words):
print("'{0}' occurs {1} times".format(word, words[word]))
文件的读与写
open(filename, encoding = 'utf8') #for reading text
open(filename, 'w', encoding = 'utf8') #for writing text
惯用方法:
for line in open(filename, encoding = 'utf8'):
process(line)
readlines()方法将整个文件读入字符串列表
字典内涵
{keyexpression:valueexpression for key, value in iterable}
{keyexpression:valueexpression for key, value in iterable if condtion}
file_sizes = {name: os.path.getsize(name) for name in os.listdir(".")}
file_sizes = {name: os.path.getsize(name) for name in os.listdir(".")
if os.path.isfile(name)}
inverted_d = {v: k for k, v in d.items()}#用于反转字典,但是对值有要求
3.3.3 默认字典
解释不清楚
import collections
words = collections.defaultdict(int)
3.3.4 有序字典
d = collections.OrderedDict([('z',-4), ('e', 19), ('k', 7)])
注意,如果括号里面传入的无序的dict,或关键字参数,那么顺序将是任意的(我在没看出来任意来)。
3.4 组合数据类型的迭代与复制
3.4.1 迭代子、迭代操作与函数 (查询有关迭代子的函数点这)
语法 | 描述 |
---|---|
s + t | 返回一个序列,该序列是序列s与t的连接 |
s * n | 返回一个序列,该序列是序列s的n个副本的连接 |
x in i | 如果项x出现在iterable i 中,就返回True,not in 进行的测试则相反 |
all(i) | 如果iterable i 中的每一项都被评估为True,就返回True |
any(i) | 如果iterable i中的任意项都被评估为True,就返回True |
enumerate(i, start) | 通常用于for ... in 循环中,提供一个(index, item)元组序列,其中的索引起始值为0或start |
len(x) | 返回x的“长度”,如果x是组合数据类型,那么返回的是其中数据项数;如果x是一个字符串,那么返回的是其中包含的字符数 |
max(i,key) | 返回iterable i 中的最大的项,如果给定的是key函数,就返回key值最大的项 |
min(i, key) | ... |
range(start, stop, step) | 返回一个整数迭代子。使用一个参数(stop)时,迭代子的取值范围从0到stop-1;使用参数(start,stop)时,迭代子取值范围从start到stop-1;3个参数全部使用时,迭代范围从start到stop-1,但每俩个值之间间隔step |
reversed(i) | 返回一个迭代子,该迭代子以反序从迭代子i中返回项#dict 不行 有序字典倒是可以 |
sorted(i, key, reverse) | 以排序后顺序从迭代子i返回项,key用于提供DSU(修饰、排序、反修饰)排序,如果reverse为True,则排序以反序进行 |
sum(i,start) | 返回iterable i 中项的和,加上start(默认为0),i 可以包含字符串 |
zip(i1, ... ,iN | 返回元组的迭代子,使用迭代子i1到iN |
x = [-2, 9, 7, -4, 3]
all(x), any(x), len(x), min(x), max(x), sum(x)
#(True, True, 5, -4, 9, 13)
iter() and next()
iter() 将返回一个用于传递给函数的对象的迭代子,如果该对象无法进行迭代,则产生一个TypeError (还有另外一种用法,关于参数和哨点值,另外再说吧(P114)).
next()会依次返回每个相继的数据项,直到没有数据项时产生StopIteration异常。
product = 1
for i in [1, 2, 3, 4]:
product *= i
print(product)
与下面的是一样的:
product = 1
i = iter([1, 2, 3, 4])
while True:
try:
product *= next(i)
except StopIteration:
break
print(product)
实例:程序读入一个forename文件与一个surname文件,创建俩个列表,之后创建test-names1.txt并向其中写入100个随机的名称
import random
def get_fornames_and_surnames():
forenames = []
surnames = []
for names, filename in ((fornames, 'data/forenames.txt'),
(surnames, 'data/surnames.txt')):
for name in open(filename, encoding='utf8'):
names.append(name.rstrip()) #删除字符串末尾指定字符(默认为空格)
return forenames, surnames
forenames, surnames = get_fornames_and_surnames()
fh = open('test-names.txt', 'w', encoding='utf8')
for i in range(100):
line = "{0} {1}\n".format(random.choice(forenames),
random.choice(surnames))
fh.write(line)
fh.close()
zip()
zip()会返回一个迭代子,每个元素是一个元组。
for t in zip(range(4), range(0, 10, 2), range(1, 10, 2)):
print(t)
#(0, 0, 1)
#(1, 2, 3)
#(2, 4, 5)
#(3, 6, 7)
#注意,即便传入的迭代子长度不一致,依然可以使用,取小。
for i in zip([1, 2], [1, 2, 3]):
print(i)
#(1, 1)
# (2, 2)
sorted() and reversed()
list(range(6)) #[0, 1, 2, 3, 4, 5]
list(reversed(range(6))) #[5, 4, 3, 2, 1, 0]
x = []
for t in zip(range(-10, 0, 1), range(0, 10, 2), range(1, 10, 2)):
x += t
x
#[-10, 0, 1, -9, 2, 3, -8, 4, 5, -7, 6, 7, -6, 8, 9]
sorted(x)
#[-10, -9, -8, -7, -6, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
sorted(x, reverse = True)
#[9, 8, 7, 6, 5, 4, 3, 2, 1, 0, -6, -7, -8, -9, -10]
sorted(x, key = abs)
#[0, 1, 2, 3, 4, 5, 6, -6, -7, 7, -8, 8, -9, 9, -10]
key关键字传入函数或方法,相当于先将x中的元素“修饰”,再对“修饰”后的元素加以排列得到一个序,最后的结果是x以这个序进行排列。
x = list(zip((1, 3, 1, 3), ('pram', 'dorie', 'kayak', 'canoe')))
print(x)
print(sorted(x))
def swap(t):
return t[1], t[0]
print(sorted(x, key=swap))
#[(1, 'pram'), (3, 'dorie'), (1, 'kayak'), (3, 'canoe')]
#[(1, 'kayak'), (1, 'pram'), (3, 'canoe'), (3, 'dorie')]
#[(3, 'canoe'), (3, 'dorie'), (1, 'kayak'), (1, 'pram')]
注意:sorted()只适用于可以进行相互比较的组合类型
sorted([1, '1')] #TypeError
不过,对于上面这种情况,有一个处理方法: key = int/float,当然这也只是对字符串为“数字”的特殊情形。
3.4.2 组合类型的复制
浅拷贝
a = [1, '2', 3]
b = a
a[1] = 'two'
a, b #([1, 'two', 3], [1, 'two', 3])
a = [1, ['2', 5], 3]
b = a[:]
a[0] = 2
b[1][0] = 'two'
a, b #([2, ['two', 5], 3], [1, ['two', 5], 3])
普通的 a=b,只是使用了对象引用,a, b指向了同一个内存对象
.copy()
dict()
list()
set()
a[:]
这些都会返回一个浅拷贝
深拷贝
a = [1, ['2', 5], 3]
b = copy.deepcopy(a)
a[0] = 2
b[1][0] = 'two'
a, b #([2, ['2', 5], 3], [1, ['two', 5], 3])
实例
生成用户名
import collections
import string
import sys
#在python中全大写的变量一般为常量,这是个约定
ID = 0
FORENAME = 1
MIDDLENAME = 2
SURNAME = 3
#命名元组
User = collections.namedtuple('User', 'username forename middlename surname id')
def Main():
if len(sys.argv) == 1 or sys.argv[1] in ('-h', '-help'):
print('useage: {0} file1 [file2 [... fileN]]'.format(
sys.argv[0]))
sys.exit()
usernames = set()
users = {}
for filename in sys.argv[1:]:
for line in open(filename, encoding='utf8'):
line = line.rstrip()
if line:
user = process_line(line, usernames)
users[(user.username.lower(),
user.forename.lower(),
user.id)] = user
print_users(users)
def process_line(line, usernames):
fields = line.split(':')
username = generate_username(fields, usernames)
user = User(username, fields[FORENAME], fields[MIDDLENAME],
fields[SURNAME], fields[ID])
return user
def generate_username(fields, usernames):
username = (fields[FORENAME][0] + fields[MIDDLENAME][:1] +
fields[SURNAME]).replace('-', '').replace("'", "")
username = original_name = username[:8].lower()
count = 1
while username in usernames:
username = "{0}{1}".format(original_name, count)
count += 1
usernames.add(username)
return username
def print_users(users):
namewidth = 32
usernamewidth = 9
print("{0:<{nw}} {1:^6} {2:{uw}}".format(
'Name', 'ID', 'Username', nw = namewidth, uw = usernamewidth))
print("{0:-<{nw}} {0:-<6} {0:-<{uw}}".format(
'', nw=namewidth, uw=usernamewidth))
for key in sorted(users):
user = users[key]
inital = ''
if user.middlename:
inital = ' ' + user.middlename[0]
name = "{0.surname}, {0.forename}{1}".format(user, inital)
print("{0:.<{nw}} {1.id:4} {1.username:{uw}}".format(
name, user, nw = namewidth, uw = usernamewidth))
if __name__ == '__main__':
Main()
输入是这样的:
输出是这样的:
小惊喜 m[:1]来代替m[0] 防止产生IndexError
s = ''
s[0] #报错
s[:1]#不会报错s[:k]都不会报错,只是s[:1]在s非空的情况下满足取首项的要求
处理统计信息
import collections
import string
import sys
import math
Statistics = collections.namedtuple('Statistic',
'mean mode median std_dev')
def main():
if len(sys.argv) == 1 or sys.argv[1] in {'-h', '-help'}:
print('usage: {0} file1 [file2 [... fileN]'.format(
sys.argv[0]))
sys.exit()
numbers = []
frequencies = collections.defaultdict(int)
for filename in sys.argv[1:]:
read_data(filename, numbers, frequencies)
if numbers:
statistics = calculate_statistics(numbers, frequencies)
print_results(len(numbers), statistics)
else:
print('no numbers found')
def read_data(filename, numbers, frequencies):
for lino, line in enumerate(open(filename, encoding='utf8'),
start = 1):
for x in line.split():
try:
number = float(x)
numbers.append(number)
frequencies[number] += 1
except ValueError as err:
print('{filename}:{lino}:skipping {x}:{err}'.format(
**locals()))
def calculate_statistics(numbers, frequencies):
mean = sum(numbers) / len(numbers)
mode = calculate_mode(frequencies, 3)
median = calculate_median(numbers)
std_dev = calculate_std_dev(numbers, mean)
return Statistics(mean, mode, median, std_dev)
def calculate_mode(frequencies, maximum_modes):
highest_frequency = max(frequencies.values())
mode = [number for number, frequency in frequencies.items()
if frequency == highest_frequency]
if not (1 <= len(mode) <= maximum_modes):
mode = None
else:
mode.sort()
return mode
def calculate_median(numbers):
numbers = sorted(numbers)
middle = len(numbers) // 2
median = numbers[middle]
if len(numbers) %2 == 0:
median = (median + numbers[middle - 1]) / 2
return median
def calculate_std_dev(numbers, mean):
total = 0
for number in numbers:
total += (number - mean) **2
variance = total / (len(numbers) - 1)
return math.sqrt(variance)
def print_results(count, statistics):
real = '9.2f'
if statistics.mode is None:
modeline = ''
elif len(statistics.mode) == 1:
modeline = 'mode = {0:{fmt}}\n'.format(
statistics.mode[0], fmt=real)
else:
modeline = ("mode = ["+
",".join(["{0:.2f}".format(m)
for m in statistics.mode]) + "]\n")
print("""\
count = {0:6}
mean = {mean:{fmt}}
median = {median:{fmt}}
{1}\
std.dev. = {std_dev:{fmt}}""".format(
count, modeline, fmt=real, **statistics._asdict()))
if __name__ == '__main__':
main()
小惊喜 print 三引号
上面的例子中出现了三引号,三引号使得程序以我们所可理解的方式展示文本(大概就是不用加\n之类的)。
print("""
aaaaa
ddddd
""")
aaaaa
ddddd
print("""
aaaaa \
ddddd
""")
aaaaa ddddd
注意这个时候 \ 可以起到把空行转义掉,回到原来的形状。
print("""
aaaaa\n\
ddddd
""")
aaaaa
ddddd