本章将深入探讨以下Python内置的集合(collection):
- str——不可变的Unicode码位字符串序列;
- list——可变的对象序列;
- dict——可变的字典,它将不可变的键映射到可变的对象;
- tuple——不可变的对象序列;
- range——对于整数的等差序列;
- set——包含唯一、可变对象的可变集。
最后介绍动协议(protocol),是协议将集合统一到一起,从而我们能够以一致和可预测的方式使用它们。
5.1 tuple——不可变的对象序列
元组(tuple)可以包含任意对象的不可变序列。一旦创建了元组,就不能替换或删除其中的对象,并且不能向它添加新的元素。
5.1.1 元组字面量
元组与列表具有相似的字面量语法,只是元组是由圆括号包裹而不是方括号。
>>> t = ('Norway', 4.953, 3)
>>> t
('Norway', 4.953, 3)
5.1.2 访问元组中的元素
可以在方括号里使用从0开始的索引来访问元组中的元素:
>>> t[0]
'Norway'
>>> t[2]
3
5.1.3 元组的长度
可以使用内置的len()函数来确定元组中元素的个数:
>>> len(t)
3
5.1.4 迭代元组
可以使用for循环迭代元组:
>>> for item in t:
... print(item)
...
Norway
4.953
3
5.1.5 连接与重复元组
可以使用加号连接元组:
>>> t + (338.0, 265E9)
('Norway', 4.953, 3, 338.0, 265000000000.0)
使用乘号重复元组中的元素:
>>> t * 3
('Norway', 4.953, 3, 'Norway', 4.953, 3, 'Norway', 4.953, 3)
内嵌的元组
由于元组可以包含任何对象,所以可以使用嵌套的元组:
>>> a = ((220, 284), (1184, 1210), (2620, 2924), (5020, 5564), (6232, 6368))
重复应用索引运算符来获取内部元素:
>>> a[2][1]
2924
5.1.6 单个元素的元组
具有单个元素和一个逗号的元组将被解析为单个元素的元组:
>>> k = (391,)
>>> k
(391,)
>>> type(k)
<class 'tuple'>
5.1.7 空元组
只用空括号来指定一个空元组:
>>> a = ()
>>> a
()
>>> type(a)
<class 'tuple'>
5.1.9 返回与拆包元组
当函数返回多个值时,通常会使用返回与拆包元组功能。
>>> def minmax(items):
... return min(items), max(items)
...
>>> minmax([83, 33, 84, 32, 85, 31, 86])
(31, 86)
元组拆包(tuple unpacking)是一种所谓的解构操作(destructuring operation),可以通过该操作将数据结构拆包成命名引用。
例如,可以将minmax()函数结果赋值给两个新引用:
>>> lower, upper = minmax([83, 33, 84, 32, 85, 31, 86])
>>> lower
31
>>> upper
86
该功能也适用于嵌套的元组:
>>> (a, (b, (c,d))) = (4, (3, (2, 1)))
>>> a
4
>>> b
3
>>> c
2
>>> d
1
5.1.10 通过元组拆包交换变量
元组拆包引出了一个优雅的Python惯用句法,交换两个(或多个)变量:
>>> a = 'jelly'
>>> b = 'bean'
>>> a, b = b, a
>>> a
'bean'
>>> b
'jelly'
5.2 元组构造函数
使用已经存在的集合对象(如列表)元组,可以使用tuple()构造函数。
>>> tuple([561, 1105, 1729, 2465])
(561, 1105, 1729, 2465)
下面使用字符串创建一个包含字符的元组:
>>> tuple('Carmichael')
('C', 'a', 'r', 'm', 'i', 'c', 'h', 'a', 'e', 'l')
成员资格测试
使用in运算符来测试成员资格:
>>> 5 in (3, 5, 17, 257, 65535)
True
使用not in运算符来测试非成员资格:
>>> 5 not in (3, 5, 17, 257, 65535)
False
5.3 字符串实战
深入探讨str类型的功能
5.3.1 字符串的长度
使用内置的len()函数来确定字符串的长度。
>>> len('Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch')
58
这是欧洲最长的地名,位于威尔士安格尔西岛的火车站。
5.3.2 连接字符串
加号运算符用于字符串的连接:
>>> 'New' + 'found' + 'land'
'Newfoundland'
还可以使用相关的增强赋值运算符:
>>> s = 'New'
>>> s += 'found'
>>> s += 'land'
>>> s
'Newfoundland'
注意:字符串是不可变的,每使用一次增强赋值运算符就会给s绑定一个新的字符串对象。修改s是可以实现的,因为s是对象的引用,而还是对象本身。也就是说,尽管字符串本身是不可变的,但它们的引用是可变的。
5.3.3 拼接字符串
要拼接大量字符串时,请避免使用+或+=运算符。应该首选join()方法,因为它更有效率。这是因为前者会产生大量的临时数据,从而导致内存分配和复值的成本大大增加。
join()是一个str的方法,它将一个字符串的集合作为参数,并通过在每个字符串之间插入一个分隔符来生成一个新的字符串。这个分隔符就是调用join()的字符串。
>>> colors = ';'.join(['#45ff23', '#2321fa', '#1298a3', '#a32312'])
>>> colors
'#45ff23;#2321fa;#1298a3;#a32312'
用空字符串来调用join()方法,广泛且高效的用于将字符串集合连接到一起。
>>> ''.join(['high', 'way', 'man'])
'highwayman'
5.3.4 拆分字符串
使用带参数的split()方法拆分字符串
>>> colors.split(';')
['#45ff23', '#2321fa', '#1298a3', '#a32312']
可以通过可选参数指定用于拆分字符串的字符串(而不仅仅是字符)
>>> 'eggsandbaconandspam'.split('and')
['eggs', 'bacon', 'spam']
5.4 禅之刻
最初的做法可能不太显而易见
在空字符串上调用join,简单而又高效
5.4.1 分割字符串
字符串方法partition(),将一个字符串划分为3个部分——分隔符前的部分、分隔符本身以及分隔符之后的部分:
>>> 'unforgettable'.partition('forget')
('un', 'forget', 'table')
partition()方法返回一个元组,通常与元组拆包一起使用:
>>> departure, separator, arrival = 'London:Edinburgh'.partition(':')
>>> departure
'London'
>>> arrival
'Edinburgh'
对不感兴趣的值可以使用下划线作为变量名称。Python语言不会以特殊的方式来处理变量名称,但是有一个不成文的约定,下划线变量用于表示未使用的或虚拟的值。
>>> origin, _, destination = 'Seattle-Boston'.partition('-')
许多支持Python语法的开发工具都支持该约定。这些工具遇到下划线时,就不会出现未使用的变量的警告。
5.4.2 字符串格式化
format()字符串方法。可以在任何包含所谓替换字段(replacement field)的字符串上调用format()方法,替换字段通常由花括号包裹。作为参数传入format()的对象将被转换为字符串,并用于填充替换字段。
>>> 'The age of {0} is {1}'.format('Jim', 32)
'The age of Jim is 32'
字段名称可能会被多次使用:
>>> "The age of {0} is {1}. {0}'s birthday is on {2}".format('Fred', 24, 'October 31')
"The age of Fred is 24. Fred's birthday is on October 31"
如果字段名称只用一次,并且与参数的顺序一致,则可以省略它们:
>>> 'Reticulating spline {} of {}.'.format(4, 23)
'Reticulating spline 4 of 23.'
如果向format()传入关键字参数,则可以使用命名字段而不是序数:
>>> 'Current position {latitude} {longitude}'.format(latitude='60N', longitude='5E')
'Current position 60N 5E'
可以在替换字段中使用方括号来索引序列:
>>> 'Galactic position x={pos[0]}, y={pos[1]}, z={pos[2]}'.format(pos=(65.2, 23.1, 82.2))
'Galactic position x=65.2, y=23.1, z=82.2'
甚至可以访问对象属性。在这里,我们将整个math模块作为关键字参数传递给format()(记住——模块也是对象),然后在替换字段中访问其两个属性:
>>> import math
>>> 'Math constants: pi={m.pi}, e={m.e}'.format(m=math)
'Math constants: pi=3.141592653589793, e=2.718281828459045'
使用格式化字符串来控制字段对齐和浮点格式化,这里的常量只显示3位小数:
>>> 'Math constants: pi={m.pi:.3f}, e={m.e:.3f}'.format(m=math)
'Math constants: pi=3.142, e=2.718'
5.4.3 其他字符串方法
用help(str)来获取其它字符串方法说明
>> help(str)
Help on class str in module builtins:
class str(object)
| str(object='') -> str
| str(bytes_or_buffer[, encoding[, errors]]) -> str
|
| Create a new string object from the given object. If encoding or
| errors is specified, then the object must expose a data buffer
| that will be decoded using the given encoding and error handler.
| Otherwise, returns the result of object.__str__() (if defined)
| or repr(object).
| encoding defaults to sys.getdefaultencoding().
| errors defaults to 'strict'.
|
| Methods defined here:
|
| __add__(self, value, /)
| Return self+value.
|
| __contains__(self, key, /)
| Return key in self.
|
| __eq__(self, value, /)
| Return self==value.
|
| __format__(self, format_spec, /)
| Return a formatted version of the string as described by format_spec.
|
| __ge__(self, value, /)
| Return self>=value.
|
| __getattribute__(self, name, /)
| Return getattr(self, name).
|
| __getitem__(self, key, /)
| Return self[key].
|
| __getnewargs__(...)
|
| __gt__(self, value, /)
| Return self>value.
|
| __hash__(self, /)
| Return hash(self).
|
| __iter__(self, /)
| Implement iter(self).
|
| __le__(self, value, /)
| Return self<=value.
|
| __len__(self, /)
| Return len(self).
|
| __lt__(self, value, /)
| Return self<value.
|
| __mod__(self, value, /)
| Return self%value.
|
| __mul__(self, value, /)
| Return self*value.
|
| __ne__(self, value, /)
| Return self!=value.
|
| __repr__(self, /)
| Return repr(self).
|
| __rmod__(self, value, /)
| Return value%self.
|
| __rmul__(self, value, /)
| Return value*self.
|
| __sizeof__(self, /)
| Return the size of the string in memory, in bytes.
|
| __str__(self, /)
| Return str(self).
|
| capitalize(self, /)
| Return a capitalized version of the string.
|
| More specifically, make the first character have upper case and the rest lower
| case.
|
| casefold(self, /)
| Return a version of the string suitable for caseless comparisons.
|
| center(self, width, fillchar=' ', /)
| Return a centered string of length width.
|
| Padding is done using the specified fill character (default is a space).
|
| count(...)
| S.count(sub[, start[, end]]) -> int
|
| Return the number of non-overlapping occurrences of substring sub in
| string S[start:end]. Optional arguments start and end are
| interpreted as in slice notation.
|
| encode(self, /, encoding='utf-8', errors='strict')
| Encode the string using the codec registered for encoding.
|
| encoding
| The encoding in which to encode the string.
| errors
| The error handling scheme to use for encoding errors.
| The default is 'strict' meaning that encoding errors raise a
| UnicodeEncodeError. Other possible values are 'ignore', 'replace' and
| 'xmlcharrefreplace' as well as any other name registered with
| codecs.register_error that can handle UnicodeEncodeErrors.
|
| endswith(...)
| S.endswith(suffix[, start[, end]]) -> bool
|
| Return True if S ends with the specified suffix, False otherwise.
| With optional start, test S beginning at that position.
| With optional end, stop comparing S at that position.
| suffix can also be a tuple of strings to try.
|
| expandtabs(self, /, tabsize=8)
| Return a copy where all tab characters are expanded using spaces.
|
| If tabsize is not given, a tab size of 8 characters is assumed.
|
| find(...)
| S.find(sub[, start[, end]]) -> int
|
| Return the lowest index in S where substring sub is found,
| such that sub is contained within S[start:end]. Optional
| arguments start and end are interpreted as in slice notation.
|
| Return -1 on failure.
|
| format(...)
| S.format(*args, **kwargs) -> str
|
| Return a formatted version of S, using substitutions from args and kwargs.
| The substitutions are identified by braces ('{' and '}').
|
| format_map(...)
| S.format_map(mapping) -> str
|
| Return a formatted version of S, using substitutions from mapping.
| The substitutions are identified by braces ('{' and '}').
|
| index(...)
| S.index(sub[, start[, end]]) -> int
|
| Return the lowest index in S where substring sub is found,
| such that sub is contained within S[start:end]. Optional
| arguments start and end are interpreted as in slice notation.
|
| Raises ValueError when the substring is not found.
|
| isalnum(self, /)
| Return True if the string is an alpha-numeric string, False otherwise.
|
| A string is alpha-numeric if all characters in the string are alpha-numeric and
| there is at least one character in the string.
|
| isalpha(self, /)
| Return True if the string is an alphabetic string, False otherwise.
|
| A string is alphabetic if all characters in the string are alphabetic and there
| is at least one character in the string.
|
| isascii(self, /)
| Return True if all characters in the string are ASCII, False otherwise.
|
| ASCII characters have code points in the range U+0000-U+007F.
| Empty string is ASCII too.
|
| isdecimal(self, /)
| Return True if the string is a decimal string, False otherwise.
|
| A string is a decimal string if all characters in the string are decimal and
| there is at least one character in the string.
|
| isdigit(self, /)
| Return True if the string is a digit string, False otherwise.
|
| A string is a digit string if all characters in the string are digits and there
| is at least one character in the string.
|
| isidentifier(self, /)
| Return True if the string is a valid Python identifier, False otherwise.
|
| Call keyword.iskeyword(s) to test whether string s is a reserved identifier,
| such as "def" or "class".
|
| islower(self, /)
| Return True if the string is a lowercase string, False otherwise.
|
| A string is lowercase if all cased characters in the string are lowercase and
| there is at least one cased character in the string.
|
| isnumeric(self, /)
| Return True if the string is a numeric string, False otherwise.
|
| A string is numeric if all characters in the string are numeric and there is at
| least one character in the string.
|
| isprintable(self, /)
| Return True if the string is printable, False otherwise.
|
| A string is printable if all of its characters are considered printable in
| repr() or if it is empty.
|
| isspace(self, /)
| Return True if the string is a whitespace string, False otherwise.
|
| A string is whitespace if all characters in the string are whitespace and there
| is at least one character in the string.
|
| istitle(self, /)
| Return True if the string is a title-cased string, False otherwise.
|
| In a title-cased string, upper- and title-case characters may only
| follow uncased characters and lowercase characters only cased ones.
|
| isupper(self, /)
| Return True if the string is an uppercase string, False otherwise.
|
| A string is uppercase if all cased characters in the string are uppercase and
| there is at least one cased character in the string.
|
| join(self, iterable, /)
| Concatenate any number of strings.
|
| The string whose method is called is inserted in between each given string.
| The result is returned as a new string.
|
| Example: '.'.join(['ab', 'pq', 'rs']) -> 'ab.pq.rs'
|
| ljust(self, width, fillchar=' ', /)
| Return a left-justified string of length width.
|
| Padding is done using the specified fill character (default is a space).
|
| lower(self, /)
| Return a copy of the string converted to lowercase.
|
| lstrip(self, chars=None, /)
| Return a copy of the string with leading whitespace removed.
|
| If chars is given and not None, remove characters in chars instead.
|
| partition(self, sep, /)
| Partition the string into three parts using the given separator.
|
| This will search for the separator in the string. If the separator is found,
| returns a 3-tuple containing the part before the separator, the separator
| itself, and the part after it.
|
| If the separator is not found, returns a 3-tuple containing the original string
| and two empty strings.
|
| removeprefix(self, prefix, /)
| Return a str with the given prefix string removed if present.
|
| If the string starts with the prefix string, return string[len(prefix):].
| Otherwise, return a copy of the original string.
|
| removesuffix(self, suffix, /)
| Return a str with the given suffix string removed if present.
|
| If the string ends with the suffix string and that suffix is not empty,
| return string[:-len(suffix)]. Otherwise, return a copy of the original
| string.
|
| replace(self, old, new, count=-1, /)
| Return a copy with all occurrences of substring old replaced by new.
|
| count
| Maximum number of occurrences to replace.
| -1 (the default value) means replace all occurrences.
|
| If the optional argument count is given, only the first count occurrences are
| replaced.
|
| rfind(...)
| S.rfind(sub[, start[, end]]) -> int
|
| Return the highest index in S where substring sub is found,
| such that sub is contained within S[start:end]. Optional
| arguments start and end are interpreted as in slice notation.
|
| Return -1 on failure.
|
| rindex(...)
| S.rindex(sub[, start[, end]]) -> int
|
| Return the highest index in S where substring sub is found,
| such that sub is contained within S[start:end]. Optional
| arguments start and end are interpreted as in slice notation.
|
| Raises ValueError when the substring is not found.
|
| rjust(self, width, fillchar=' ', /)
| Return a right-justified string of length width.
|
| Padding is done using the specified fill character (default is a space).
|
| rpartition(self, sep, /)
| Partition the string into three parts using the given separator.
|
| This will search for the separator in the string, starting at the end. If
| the separator is found, returns a 3-tuple containing the part before the
| separator, the separator itself, and the part after it.
|
| If the separator is not found, returns a 3-tuple containing two empty strings
| and the original string.
|
| rsplit(self, /, sep=None, maxsplit=-1)
| Return a list of the substrings in the string, using sep as the separator string.
|
| sep
| The separator used to split the string.
|
| When set to None (the default value), will split on any whitespace
| character (including \\n \\r \\t \\f and spaces) and will discard
| empty strings from the result.
| maxsplit
| Maximum number of splits (starting from the left).
| -1 (the default value) means no limit.
|
| Splitting starts at the end of the string and works to the front.
|
| rstrip(self, chars=None, /)
| Return a copy of the string with trailing whitespace removed.
|
| If chars is given and not None, remove characters in chars instead.
|
| split(self, /, sep=None, maxsplit=-1)
| Return a list of the substrings in the string, using sep as the separator string.
|
| sep
| The separator used to split the string.
|
| When set to None (the default value), will split on any whitespace
| character (including \\n \\r \\t \\f and spaces) and will discard
| empty strings from the result.
| maxsplit
| Maximum number of splits (starting from the left).
| -1 (the default value) means no limit.
|
| Note, str.split() is mainly useful for data that has been intentionally
| delimited. With natural text that includes punctuation, consider using
| the regular expression module.
|
| splitlines(self, /, keepends=False)
| Return a list of the lines in the string, breaking at line boundaries.
|
| Line breaks are not included in the resulting list unless keepends is given and
| true.
|
| startswith(...)
| S.startswith(prefix[, start[, end]]) -> bool
|
| Return True if S starts with the specified prefix, False otherwise.
| With optional start, test S beginning at that position.
| With optional end, stop comparing S at that position.
| prefix can also be a tuple of strings to try.
|
| strip(self, chars=None, /)
| Return a copy of the string with leading and trailing whitespace removed.
|
| If chars is given and not None, remove characters in chars instead.
|
| swapcase(self, /)
| Convert uppercase characters to lowercase and lowercase characters to uppercase.
|
| title(self, /)
| Return a version of the string where each word is titlecased.
|
| More specifically, words start with uppercased characters and all remaining
| cased characters have lower case.
|
| translate(self, table, /)
| Replace each character in the string using the given translation table.
|
| table
| Translation table, which must be a mapping of Unicode ordinals to
| Unicode ordinals, strings, or None.
|
| The table must implement lookup/indexing via __getitem__, for instance a
| dictionary or list. If this operation raises LookupError, the character is
| left untouched. Characters mapped to None are deleted.
|
| upper(self, /)
| Return a copy of the string converted to uppercase.
|
| zfill(self, width, /)
| Pad a numeric string with zeros on the left, to fill a field of the given width.
|
| The string is never truncated.
|
| ----------------------------------------------------------------------
| Static methods defined here:
|
| __new__(*args, **kwargs) from builtins.type
| Create and return a new object. See help(type) for accurate signature.
|
| maketrans(...)
| Return a translation table usable for str.translate().
|
| If there is only one argument, it must be a dictionary mapping Unicode
| ordinals (integers) or characters to Unicode ordinals, strings or None.
| Character keys will be then converted to ordinals.
| If there are two arguments, they must be strings of equal length, and
| in the resulting dictionary, each character in x will be mapped to the
| character at the same position in y. If there is a third argument, it
| must be a string, whose characters will be mapped to None in the result.
5.5 range——等间隔的整数集合
本节介绍区间(range),在Python3中它是集合。
区间是用于表示整数等差序列的一种类型的序列。区间是通过调用range()构造函数创建的,它没有字面量形式。最典型的是,只提供结束值,默认起始值是0。
>>> range(5)
range(0, 5)
区间有时用于创建连续的整数,它可以作为循环计数器:
>>>
>>> for i in range(5):
... print(i)
...
0
1
2
3
4
注意:提供给range()的结束值是一个超过序列结尾的值,所以上面的那个循环没有输出5。
5.5.1 起始值
如有需要,可以提供一个起始值:
>>> range(5, 10)
range(5, 10)
将起始值传入list()构造函数,这是一种强制生成每个项目的简单方法:
>>> list(range(5,10))
[5, 6, 7, 8, 9]
这是所谓的半开区间约定,即结束值不包括在序列中。起初,它似乎有些奇怪,但是如果你正在处理连续区间,那么它的意义重大,因为一个区间的结尾是下一个区间的开始:
>>> list(range(10, 15))
[10, 11, 12, 13, 14]
>>> list(range(5, 10)) + list(range(10, 15))
[5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
5.5.2 步长参数
区间也支持步长参数:
>>> list(range(0, 10, 2))
[0, 2, 4, 6, 8]
注意:为了使用该步长(step)参数,我们必须提供所有的3个参数。区间对参数感兴趣,因为它通过计数参数来确定参数的含义。只提供1个参数意味着参数是结束值。2个参数是起始值(start)结束值(stop),3个参数是起始值、结束值和步长。range构造函数不支持关键字参数。
5.5.3 不使用区间:enumerate()
一个风格糟糕unPythonic的代码示例,应该尽量避免这样写!
>>> s = [0, 1, 4, 6, 13]
>>> for i in range(len(s)):
... print(s[i])
...
0
1
4
6
虽然它可以正常运行,但这绝对是unPythonic的。可以直接迭代对象自身。
>>> s = [0, 1, 4, 6, 13]
>>> for v in s:
... print(v)
...
0
1
4
6
13
如果需要一个计数器,那应该使用内置的enumerate()函数,它返回可迭代的一系列的值对,每个值对都是一个元组。每个值对的第一个元素是当前项目的索引,第二个元素是项目本身:
>>> t = [6, 372, 8862, 148800, 2096886]
>>> for p in enumerate(t):
... print(p)
...
(0, 6)
(1, 372)
(2, 8862)
(3, 148800)
(4, 2096886)
更好的方式是使用元组拆包从而避免直接处理元组:
>>> for i, v in enumerate(t):
... print('i = {}, v = {}'.format(i, v))
...
i = 0, v = 6
i = 1, v = 372
i = 2, v = 8862
i = 3, v = 148800
i = 4, v = 2096886
5.6 列表实战
5.6.1 列表的负数索引(其他序列同理)
列表(其他Python序列同理,这也适用于元组)通过提供负数(negative)索引来实现,从最后面而不是开头开始索引。
>>> r = [1, -4, 10, -16, 15]
>>> r[-1]
15
>>> r[-2]
-16
负整数从后向前、从-1开始。
5.6.2 列表切片
切片是一种扩展索引的形式,可以使用它引用列表的一部分。要使用它,需要转入半开区间的起始值和结束值,这两个值以冒号分隔,并作为方括号中的索引参数。
>>> s = [3, 186, 4431, 74400, 1048443]
>>> s[1:3]
[186, 4431]
切片区间是半开的,因此最后一个索引的值不包括在内。这个功能可以与负数索引相结合。例如,除了第一个和最后一个的所有元素:
>>> s[1:-1]
[186, 4431, 74400]
起始索引和结束索引都是可选的。
切片列表的第4个之后(包括第4个)的所有元素:
>>> s[3:]
[74400, 1048443]
将所有元素从头开始切片,不包括第4个元素:
>>> s[:3]
[3, 186, 4431]
起始切片和结束切片索引都省略,检索所有元素,这就是全切片(full slice),它是复制列表的重要惯用句法:
>>> s[:]
[3, 186, 4431, 74400, 1048443]
5.6.3 复制列表
我们知道,赋值引用永远不会复制对象,而只是复制对对象的引用。
全切片虽然可以复制列表,但并不是通俗易懂。可以通过使用copy()方法和list()构造函数等更可读的方式来复制列表。
>>> u = s.copy()
>>> u is s
False
>>>
>>> v = list(s)
>>> v is s
False
5.6.4 浅复制
注意,以上所有的这些技术(全切片、opy()方法和list()构造函数)都会执行浅复制(shallow copy)。也就是说,它们创建一个新的列表,其中包含与源列表相同的对象的引用,但是它们不复制所引用的对象。例如,嵌套列表,内部列表为可变对象。
>>> a = [[1, 2], [3, 4]]
>>> b = a[:]
>>> a is b
False
>>> a == b
True
>>> # 但是请注意,这些不同列表的引用指向了等值的对象:
>>> a[0]
[1, 2]
>>> b[0]
[1, 2]
>>> # 实际上,它们指向同一个对象:
>>> a[0] is b[0]
True
>>> # 这种情况一直存在,直到将a的第一个元素重新绑定到新构建的列表中:
>>> a[0] = [8, 9]
>>> a[0]
[8, 9]
>>> b[0]
[1, 2]
>>> # a 的第二个元素和b的第二个元素仍然指向相同的对象。
>>> # 下面通过修改列表的对象来说明这一点:
>>> a[1].append(5)
>>> a[1]
[3, 4, 5]
>>> b[1]
[3, 4, 5]
>>> a
[[8, 9], [3, 4, 5]]
>>> b
[[1, 2], [3, 4, 5]]
5.6.5 重复列表
字符串、元组和列表都支持使用乘法运算符进行重复(replace)。
>>> c = [21, 37]
>>> d = c * 4
>>> d
[21, 37, 21, 37, 21, 37, 21, 37]
最常用的是将大小已知的列表初始化为常量值,例如零:
>>> [0] * 9
[0, 0, 0, 0, 0, 0, 0, 0, 0]
请注意,在存在可变元素的情况下,仍然会存在相同的陷阱(浅复制),因为只是重复对每个元素的引用,而不是复制该值。用作为可变元素的内嵌列表来说明:
>>> s = [ [-1, +1] ] * 5
>>> s
[[-1, 1], [-1, 1], [-1, 1], [-1, 1], [-1, 1]]
>>> s[2].append(7)
>>> s
[[-1, 1, 7], [-1, 1, 7], [-1, 1, 7], [-1, 1, 7], [-1, 1, 7]]
5.6.6 使用index()查找列表元素
要查找列表中的元素,可以使用index()方法,只要传入你要查找的对象即可,它会对元素进行等价比较,直到找到要查找的元素为止:
>>> w = 'the quick brown fox jumps over the lazy dog'.split()
>>> w
['the', 'quick', 'brown', 'fox', 'jumps', 'over', 'the', 'lazy', 'dog']
>>> i = w.index('fox')
>>> i
3
>>> w[i]
'fox'
如果查找的值不存在,那么会收到一个ValueError错误:
>>> w.index('unicorn')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: 'unicorn' is not in list
我们将在第6章学习如何优雅地处理这种错误。
5.6.7 使用count()和in进行成员资格测试
使用count()查找匹配元素的个数:
>>> w.count('the')
2
如果只是想测试成员资格,那么可以使用in运算符:
>>> 37 in [1, 78, 9, 37, 34, 53]
True
使用not in可以进行非成员资格测试:
>>> 78 not in [1, 78, 9, 37, 34, 53]
False
5.6.8 使用del按照索引删除列表元素
del关键字接收一个参数,这个参数是对列表元素的引用,通过该引用我们可以将元素从列表中删除。
>>> u = 'jackdaws love my big sphinx of quartz'.split()
>>> u
['jackdaws', 'love', 'my', 'big', 'sphinx', 'of', 'quartz']
>>> del u[3]
>>> u
['jackdaws', 'love', 'my', 'sphinx', 'of', 'quartz']
5.6.9 使用remove()按照值删除列表元素
可以使用remove()方法通过值而不是位置来删除元素:
>>> u.remove('jackdaws')
>>> u
['love', 'my', 'sphinx', 'of', 'quartz']
尝试用remove()删除不存在的项目也会导致引发ValueError错误:
>>> u.remove('pyramid')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: list.remove(x): x not in list
5.6.10 向列表中插入元素
使用insert()方法将项目插入列表中,该方法接收新项目的索引和新项目的自身:
>>> a = 'I accidentally the whole universe'.split()
>>> a
['I', 'accidentally', 'the', 'whole', 'universe']
>>> a.insert(2, 'destroyed')
>>> a
['I', 'accidentally', 'destroyed', 'the', 'whole', 'universe']
>>> ' '.join(a)
'I accidentally destroyed the whole universe'
5.6.11 连接列表
使用加法运算符连接列表会生成新列表,而不会修改任一操作数:
>>> m = [2, 1, 3]
>>> n = [4, 7, 11]
>>> k = m + n
>>> k
[2, 1, 3, 4, 7, 11]
>>> m
[2, 1, 3]
>>> n
[4, 7, 11]
而增强运算符+=会原地修改最初的变量:
>>> k += [18, 29, 47]
>>> k
[2, 1, 3, 4, 7, 11, 18, 29, 47]
使用extend()方法也会有相同的效果:
>>> k.extend([76, 129, 199])
>>> k
[2, 1, 3, 4, 7, 11, 18, 29, 47, 76, 129, 199]
增强赋值和extend()方法可以与可迭代序列一起使用。
5.6.12 重排列表元素
本节介绍重排元素的两个操作:反转和排序。
通过简单地调用reverse()方法可以原地反转列表:
>>> g = [1, 11, 21, 1211, 112111]
>>> g.reverse()
>>> g
[112111, 1211, 21, 11, 1]
通过调用sort()方法可以原地对列表进行排序:
>>> d = [5, 17, 41, 29, 71, 149, 3299, 7, 13, 67 ]
>>> d.sort()
>>> d
[5, 7, 13, 17, 29, 41, 67, 71, 149, 3299]
sort()方法接收两个可选参数,即key和reverse。当reverse被设置为True时,可以进行降序排列:
>>> d.sort(reverse=True)
>>> d
[3299, 149, 71, 67, 41, 29, 17, 13, 7, 5]
key参数它接收任何可调用(callable)对象,然后用于从每个项目提取一个关键字(key)。然后,这些项目将根据关键字的相对顺序进行排序。
Python中有几种类型的可调用对象,目前我们遇到的唯一一个可调用对象是一个简单的函数len()。len()函数是一个可调用对象,用于确定集合(如字符串的长度)。
>>> h = 'not perplexing do handwriting family where I illegibly know doctors'.split()
>>> h
['not', 'perplexing', 'do', 'handwriting', 'family', 'where', 'I', 'illegibly', 'know', 'doctors']
>>> h.sort(key=len)
>>> h
['I', 'do', 'not', 'know', 'where', 'family', 'doctors', 'illegibly', 'perplexing', 'handwriting']
>>> ' '.join(h)
'I do not know where family doctors illegibly perplexing handwriting'
5.6.13 异地重排
有时原地排序或反转可能还是我们想要的。例如,它可能会导致函数的参数被修改,从而产生令人意想不到的结果。对于reverse()和sort()列表方法的异地等效方法,你可以使用reversed()和sorted()内置函数,它们分别返回反向迭代器和新的排序列表。
>>> x = [4, 9, 2, 1]
>>> y = sorted(x)
>>> y
[1, 2, 4, 9]
>>> x
[4, 9, 2, 1]
也可以使用reversed()函数:
>>> p = [9, 3, 1, 0]
>>> q = reversed(p)
>>> q
<list_reverseiterator object at 0x00000222D587B160>
>>> list(q)
[0, 1, 3, 9]
>>>
注意:我们使用列表构造函数来对reversed()的结果求值。这是因为reversed()返回一个迭代器,我们将在后面详细介绍该主题。
这些函数的优点就是它们可以在任何有限的迭代源对象上工作。
5.7 字典
字典在 许多Python程序(包括Python解释器本身)中占据核心位置。
字典字面量是用花括号包裹的,包含逗号分隔的键值对,每个键值对使用冒号绑定:
>>> urls = {'goole':'Googler 的网址',
... 'Twitter':'Twitter的网址',
... 'Microsoft':'Microsoft的网址'}
# 通过键访问其中的值
>>> urls['Twitter']
'Twitter的网址'
字典中的键必须是唯一的,但值可以重复。
字典中的对象顺序是随机的。
与其他集合一样,字典也有一个命名构造函数dict(),该函可以将其他类型转换为字典。我们可以使用构造函数将存储在元组中的一系列键值对复制生成一个字典:
>>> names_and_ages = [('Alice', 32), ('Bob', 48), ('Charlie', 28), ('Daniel', 32)]
>>> d = dict(names_and_ages)
>>> d
{'Alice': 32, 'Bob': 48, 'Charlie': 28, 'Daniel': 32}
只要是合法的Python标识符,我们甚至可以直接向dict()传入关键字参数,从而创建一个字典:
>>> phonetic = dict(a='alfa', b='bravo', c='charlie', d='delta', e='echo', f='foxtrot')
>>> phonetic
{'a': 'alfa', 'b': 'bravo', 'c': 'charlie', 'd': 'delta', 'e': 'echo', 'f': 'foxtrot'}
5.7.1 复制字典
与列表一样,字典默认情况下也是浅复制,仅复制对键对象和值对象的引用,而不复制对象本身。复制字典有两种方法,一种是使用copy()方法:
>>> d = dict(goldenrod=0xDAA520, indigo=0x4B0082, seashell=0xFFF5EE)
>>> e = d.copy()
>>> e
{'goldenrod': 14329120, 'indigo': 4915330, 'seashell': 16774638}
第二种方法是将已存在的字典传入到dict()构造函数中:
>>> f = dict(e)
>>> f
{'goldenrod': 14329120, 'indigo': 4915330, 'seashell': 16774638}
5.7.2 更新字典
如果需要使用另一个字典的定义来扩展一个字典,则可以使用update()方法。在要更新的字典上调用该方法,并且向其传入要合并的字典的内容:
>>> g = dict(wheat=0xF5DEB3, khaki=0xF0E68C, crimson=0xDC143C)
>>> f.update(g)
>>> f
{'goldenrod': 14329120, 'indigo': 4915330, 'seashell': 16774638, 'wheat': 16113331, 'khaki': 15787660, 'crimson': 14423100}
如果update()的参数包含已经存在于目标字典中的键,则目标字典中与这些键相关联的值将被替换为参数中的相应值:
>>> stocks = {'GOOG':891, 'AAPL':416, 'IBM':194}
>>> stocks.update({'GOOG':894, 'YHOO':25})
>>> stocks
{'GOOG': 894, 'AAPL': 416, 'IBM': 194, 'YHOO': 25}
5.7.3 迭代字典的键
字典只会在每次迭代中生成关键字,可以通过使用方括号运算符来查找、检索相应的值:
>>> colors = dict(aquamarine='#7FFFD4', burlywood='#DEB887',
... chartreuse='#FFF00', honeydew='#F0FFF0',
... cornflower='6495ED', firebick='#B22222',
... maroon='#B03060', sienna='#A0522D')
>>>
>>> for key in colors:
... print('{key} => {value}'.format(key=key, value=colors[key]))
...
aquamarine => #7FFFD4
burlywood => #DEB887
chartreuse => #FFF00
honeydew => #F0FFF0
cornflower => #6495ED
firebick => #B22222
maroon => #B03060
sienna => #A0522D
5.7.4 迭代字典的值
value()字典方法它返回该字典中值的可迭代视图,并不会复制字典中的值:
>>> for value in colors.values():
... print(value)
...
#7FFFD4
#DEB887
#FFF00
#F0FFF0
#6495ED
#B22222
#B03060
#A0522D
没有一种有效且方便的方法可以根据值检索相应的键,所以我们只能输出这些值。
还有一个keys()方法可以迭代字典的键,由于迭代字典对象会直接生成对应的键,所以,该方法不太常用:
>>> for key in colors.keys():
... print(key)
...
aquamarine
burlywood
chartreuse
honeydew
cornflower
firebick
maroon
sienna
5.7.5 迭代键值对
字典中的每个键值对都被称为一个项目(item),可以使用items()字典方法获取项目的可迭代视图。当迭代items()时,视图将产生键值对,这些键值对都是元组类型。
通过在for语句中使用元组拆包,我们可以在一个操作中获取键和值,而无需进行额外的查找:
>>> for key, value in colors.items():
... print('{key} => {value}'.format(key=key, value=value))
...
aquamarine => #7FFFD4
burlywood => #DEB887
chartreuse => #FFF00
honeydew => #F0FFF0
cornflower => #6495ED
firebick => #B22222
maroon => #B03060
sienna => #A0522D
5.7.6 字典键的成员资格测试
对于字典的成员资格测试,我们可以对键使用in和not in运算符:
>>> 'honeydew' in colors
True
>>> 'brown' not in colors
True
5.7.7 删除字典的项目
和列表一样,我们可以使用del关键字从字典中删除一个项目:
>>> del colors['honeydew']
>>> colors
{'aquamarine': '#7FFFD4', 'burlywood': '#DEB887', 'chartreuse': '#FFF00', 'cornflower': '#6495ED', 'firebick': '#B22222', 'maroon': '#B03060', 'sienna': '#A0522D'}
5.7.8 字典的可读性
字典中的键应该是不可变的,但是可以修改它的值。
>>> m = {'H': [1, 2, 3],
... 'He': [3, 4],
... 'Li':[6, 7],
... 'Be':[7, 9, 10],
... 'B': [10, 11],
... 'C': [11, 12, 13, 14]}
>>>
>>> m['H'] += [4, 5, 6, 7]
>>> m
{'H': [1, 2, 3, 4, 5, 6, 7], 'He': [3, 4], 'Li': [6, 7], 'Be': [7, 9, 10], 'B': [10, 11], 'C': [11, 12, 13, 14]}
这里,我们对list对象使用增强赋值运算符以访问‘H’,这个字典没有被修改。
当然,字典本身是可变的。我们可以向字典添加项目:
>>> m['N'] = [13, 14, 15]
>>> m
{'H': [1, 2, 3, 4, 5, 6, 7], 'He': [3, 4], 'Li': [6, 7], 'Be': [7, 9, 10], 'B': [10, 11], 'C': [11, 12, 13, 14], 'N': [13, 14, 15]}
5.7.9 美化输出
对于复合数据结构,我们可以使用Python标准库中的pprint模块来完成美化输出,该模块包含一个名为pprint的函数:
>>> from pprint import pprint as pp
请注意:如果没有将pprint函数绑定到不同名称的pp上,函数引用就将覆盖模块引用,这会导致我们无法进一步访问模块的内容。
>>> pp(m)
{'B': [10, 11],
'Be': [7, 9, 10],
'C': [11, 12, 13, 14],
'H': [1, 2, 3, 4, 5, 6, 7],
'He': [3, 4],
'Li': [6, 7],
'N': [13, 14, 15]}
5.8 集——包含唯一元素的无序集合
集(set)数据类型是一种无序的集合,且其中的元素是唯一的。集是可变的,可以向集中添加和删除元素,但每个元素本身必须是不可变的,非常像字典的键。
集是不同元素的无序组。
集具有与字典非常相似的字面量形式,它也是由花括号包裹,但每个项目都是单个对象,而不是由冒号组合的键值对:
>>> p = {6, 28, 496, 8128, 33550336}
>>> p
{33550336, 8128, 496, 6, 28}
>>> type(p)
<class 'set'>
当然,集的类型就是集。
5.8.1 set构造函数
空花括号,它创建一个空字典,而不是一个空集。
>>> d = {}
>>> d
{}
>>> type(d)
<class 'dict'>
为了创建空集,必须使用set()构造函数:
>>> e = set()
>>> e
set() # Python回应给我们的空集的形式
>>> type(e)
<class 'set'>
set()构造函数可以使用任何可迭代的序列创建一个集,例如列表:
>>> s = set([2, 4, 6, 16, 64, 4096, 65535, 262144])
>>> s
{64, 4096, 2, 262144, 4, 6, 16, 65535}
用字典创建集将取出所有的键:
>>> e = set(colors)
>>> e
{'firebick', 'burlywood', 'maroon', 'sienna', 'cornflower', 'chartreuse', 'aquamarine'}
集会删除输入序列中重复的值。事实上,集的一个常见用法就是从一系列对象中有效地删除重复项:
>>> t = [1, 4, 2, 1, 7, 9, 9]
>>> set(t)
{1, 2, 4, 7, 9}
5.8.2 迭代集
集也是可迭代的,尽管其顺序是任意的:
>>> for x in {1, 2, 4, 8, 16, 32}:
... print(x)
...
32
1
2
4
8
16
5.8.3 集的成员资格测试
成员资格测试是集的基本操作。与其它集合类型一样,可以使用in和not in运算符:
>>> q = {2, 9, 6, 4}
>>> 3 in q
False
>>> 3 not in q
True
5.8.4 向集中添加元素
使用add()方法向集中添加一个元素:
>>> k = {81, 108}
>>> k
{81, 108}
>>> k.add(54)
>>> k
{81, 108, 54}
>>> k.add(12)
>>> k
{81, 108, 12, 54}
向集中添加一个已经存在的元素,不会有任何效果:
>>> k.add(108)
>>> k
{81, 108, 12, 54}
然而,该操作不会产生错误。
使用update()方法可以从任何可迭代的序列(也包括另一个集)中向集添加多个元素:
>>>
>>> k.update([37, 128, 97, 37])
>>> k
{128, 97, 37, 108, 12, 81, 54}
5.8.5 从集中删除元素
从集中删除元素的方法有两种。
第一种为remove()方法,该方法要求要删除的元素存在于集中,否则会抛出KeyError:
>>> k.remove(97)
>>> k
{128, 37, 108, 12, 81, 54}
>>> k.remove(98)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 98
第二种方法为discard()方法,用该方法如果元素不是集的成员,也不会抛出错误:
>>> k.discard(54)
>>> k
{128, 37, 108, 12, 81}
>>> k.discard(98)
5.8.6 复制集
与其他内置集合类型一样,集也有一个copy()方法,它执行集的浅复制(复制引用而不是复制对象):
>>> j = k.copy()
>>> j
{128, 81, 37, 108, 12}
还可以使用set()构造函数来复制一个集:
>>> m = set(j)
>>> m
{128, 81, 37, 108, 12}
5.8.7 集的代数运算
集的代数运算包括并集(union)、交集(intersection)、补集(difference)和对称差(symmetric_difference)。可以使用集运算判断两个集是否具有子集、超集或不相交等关系。
为了展示这些方法,我们根据各种表型构建一些人名的集:
>>> blue_eyes = {'Olivia', 'Harry', 'Lily', 'Jack', 'Amelia'}
>>> blond_hair = {'Harry', 'Jack', 'Amelia', 'Mia', 'Joshua'}
>>> smell_hcn = {'Harry', 'Amelia'}
>>> taste_ptc = {'Harry', 'Lily', 'Amelia', 'Lola'}
>>> o_blood = {'Mia', 'Joshua', 'Lily', 'Olivia'}
>>> b_blood = {'Amelia', 'Jack'}
>>> a_blood = {'Harry'}
>>> ab_blood = {'Joshua', 'Lola'}
5.8.8 并集
集的并集会收集两个集中的所有元素。
要找到所有有金发、蓝眼睛或两者都有的人,可以使用union()方法:
>>> blue_eyes.union(blond_hair)
{'Lily', 'Joshua', 'Harry', 'Amelia', 'Olivia', 'Mia', 'Jack'}
union()方法是一个可交换的操作(即可以交换操作数的顺序),可以使用值等于运算符来检查结果集的等价性:
>>> blue_eyes.union(blond_hair) == blond_hair.union(blue_eyes)
True
交集
为了找出所有金发且蓝眼睛的人,可以使用intersection()方法:
>>> blue_eyes.intersection(blond_hair)
{'Harry', 'Amelia', 'Jack'}
intersection()方法只收集两个集中都存在的元素。这个操作也是可交换的:
>>> blue_eyes.intersection(blond_hair) == blond_hair.intersection(blue_eyes)
True
5.8.9 补集
为了确认那些有金发而没有蓝眼睛的人,可以使用difference()方法:
>>> blond_hair.difference(blue_eyes)
{'Mia', 'Joshua'}
difference()方法会找出所有存在于第一个集而不存在于第二个集中的元素。
difference()方法是不可交换的。
>>> blond_hair.difference(blue_eyes) == blue_eyes.difference(blond_hair)
False
1.对称差
如果想确认只有金发或者只有蓝眼睛的人,可以使用symmetric_difference()方法:
>>> blond_hair.symmetric_difference(blue_eyes)
{'Lily', 'Joshua', 'Olivia', 'Mia'}
symmetrci_difference()方法收集只在第一个集或者只在第二个集中的所有元素。
symmetric_difference()方法是可交换的:
>>> blond_hair.symmetric_difference(blue_eyes) == blue_eyes.symmetric_difference(blond_hair)
True
2.子集关系
集提供了3种断言方法用来判断集之间的关系。
issubset()方法:检查一个集是否是另一个集的子集。例如,要检查是否所有可以闻到氰化氢的人都有金发:
>>> smell_hcn.issubset(blond_hair)
True
issuperset()方法:检查第二个集中的所有元素是否存在于第一个集中。例如,要测试是否所有可以使用苯硫代氨基甲酸(PTC)的人都能闻到氰化氢:
>>> taste_ptc.issuperset(smell_hcn)
True
isdisjoint()方法:检查两个集没有公共元素。例如,你的血型是A或者O,也可能都不是:
>>> a_blood.isdisjoint(o_blood)
True
5.9 集合协议
在Python中,协议是一组操作或方法,如果一个类型要实现该协议,就必须支持这组操作或方法。我们无须像合适名义类型的语言(如C#或Java)那样,在源代码中将协议定义为独立的接口或基类,只要简单地让一个对象提供这些操作的功能实现就足够了。
对于在Python中遇到的不同集合,我们可以根据它们支持的协议来组织它们:
协 议 | 实现的集合 |
容器 | 字符串、列表、字典、区间、元组、集合、字节 |
大小 | 字符串、列表、字典、区间、元组、集合、字节 |
可迭代 | 字符串、列表、字典、区间、元组、集合、字节 |
序列 | 字符串、列表、区间、元组、字节 |
可变序列 | 列表 |
可变集合 | 集合 |
可变映射 | 字典 |
对协议的支持需要类型的特定行为。
5.9.1 容器协议
容器(container)协议要求对象支持使用in和not in运算符进行成员资格测试:
item in container
item not in container
5.9.2 大小协议
大小(sized)协议要求对象可以通过调用len(sized_collection)函数来确定集合中元素的个数。
5.9.3 可迭代协议
可迭代(iterable)提供了一种在请求时逐个生成元素的方法。迭代是一个重要的概念,将在后面用一整章来详细介绍它。
5.9.4 序列协议
序列协议要求对象通过使用由方括号包裹的整数来检索项目。
item = sequence[index]
可以使用index()来搜索项目:
i = sequence.index(item)
可以使用count()来对项目进行计数:
num = sequence.count(item)
可以使用reversed()对序列进行反转复制:
r = reversed(sequence)
另外,序列协议要求对象支持可迭代、大小以及容器协议。
5.9.5 其他协议
省略
5.10 小结
- 元组是不可变的序列类型。
- 字面量语法使用可选的圆括号包裹以逗号分隔的列表。
- 有一个值得注意的语法就是对于单元素的元组需要使用后缀的逗号。
- 元组解包一般用于多个返回值和交换变量。
- 字符串。
- 使用join()方法连接字符串比使用加法运算符和增强赋值运算符更加高效。
- partition()方法是一个有用且优雅的字符串分析工具。
- format()方法提供了一个强大的功能,它可以使用字符串值替换占位符。
- 区间。
- 区间对象代表等差序列。
- 当进行循环计数时,内建的enumerate()方法通常优于range()。
- 列表。
- 列表支持使用负数索引进行从尾部开始的索引。
- 切片语法用于复制整个或者部分列表。
- 全切片是一个用于复制列表的常用惯用句法,尽管copy()方法和list()构造函数更加通俗易懂。
- 在Python中,列表(以及其它集合类型)默认是浅复制,浅复制只是复制引用 ,而不会复制引用的对象。
- 映射键到值的字典。
- 字典中 的迭代和成员资格测试主要针对的是字典中的键。
- keys()、values()和items()方法提供了字典不同方面的视图,使用这些视图可以 方便地进行迭代。
- 集保存了一些无序且唯一的元素。
- 集支持代数运算和判断。
- 我们可以根据集合所支持的协议如可迭代、序列、映射,将内建的集合类型组织起来。
- 我们还发现:
- 下划线通常用于虚拟或者冗余的变量;
- pprint模块支持复合数据结构的美化输出。