列表生成式和生成式表达式

我们可以用 map 和 filter 达到 列表生成式的效果


In [6]:
symbols = "a%b&c$de$"
beyond_ascii = [ord(s) for s in symbols if ord(s) > 50]
beyond_ascii


Out[6]:
[97, 98, 99, 100, 101]

In [8]:
beyond_ascii = list(filter(lambda c: c > 50, map(ord, symbols)))
beyond_ascii


Out[8]:
[97, 98, 99, 100, 101]

对于上面的例子来说, filter/map 并不比 listcomp(列表生成式) 快, 后面的章节我们进一步讨论

列表生成式的多层循环

假如我们要制作两种颜色三种大小组成的 T 恤, 下面的代码是生成 T 恤的序列


In [10]:
colors = ['black', 'white']
sizes = ['S', 'M', 'L']
tshirts = [(color, size) for color in colors #两种颜色,三种尺寸的方式创建列表
                         for size in sizes]
tshirts


Out[10]:
[('black', 'S'),
 ('black', 'M'),
 ('black', 'L'),
 ('white', 'S'),
 ('white', 'M'),
 ('white', 'L')]

Python中,[], {}, () 内的换行符会被忽略,所以我们在创建列表,字典等时,不必使用 \ 来换行,列表推导式的作用只有一个,生成列表,如果想生成其它类型的序列,生成器就派上了用场。

生成器表达式

生成式表达式更省存储空间,因为它背后遵守了迭代器协议,逐个的产生项目,而不是一次构建整个列表然后将列表传递到某个构造函数中。

Genexp(生成式表达式)语法和 listcomp(列表生成式) 一样,但是它使用圆括号而不是方括号


In [3]:
symbols = "a%b&c$de$"
tuple(ord(s) for s in symbols) #如果生成器是一个函数的唯一的参数,则不用重复使用括号


Out[3]:
(97, 37, 98, 38, 99, 36, 100, 101, 36)

In [5]:
import array 
array.array('I', (ord(symbol) for symbol in symbols)) #不是唯一的参数,所以必须要加上括号


Out[5]:
array('I', [97, 37, 98, 38, 99, 36, 100, 101, 36])

Tuple

Tuple 并非只是不可变列表

Tuple 可以当成不可变序列使用,也可以当成没有字段名的记录。元组其实是对数据的记录,元组中每个元素都存放了记录中的一个字段的数据,外加这个字段的位置,正是这个位置信息给数据赋予了意义。将 tuple 当成记录时,重新排序 tuple 会破坏它表示的信息


In [8]:
lax_coordinates = (33.942, -118.4080) #经纬度
city, year, pop, chg, area = ('Tokyo', 2003, 32450, 0.66, 8014) #Tokyo 的资料,包括名称年份人口人口变化和面积
traveler_ids = [('USA', '31195855'), ('BRA', 'CE342567'), ('ESP', 'XDA205856')] #country_code,passport_number
for passport in sorted(traveler_ids): 
    print('%s/%s' % passport)


BRA/CE342567
ESP/XDA205856
USA/31195855

In [9]:
for country, _ in traveler_ids: 
    print(country)


USA
BRA
ESP

元组拆包

在上面我们将元组拆解成 city, year,... 等,接着在下面,我们用 "%s/%s" % passport 打印元组,这些例子是拆解元组的范例。任何可迭代对象都可以进行元组拆包,被可迭代对象的元素数量必须和接受这些元素的元组空档一致,除非你用星号(*)来处理多余的元素

在元组拆包中,最常见的是平行赋值,也就是将可迭代对象的项目指派给一个变量 tuple,如下所示:


In [10]:
lax_coordinates = (33.348, -117.932)
latitude, longitude = lax_coordinates # tuple 拆解
print(latitude)
print(longitude)


33.348
-117.932

不用中间变量交换值,是元组拆包的另一种应用


In [11]:
a = 3
b = 5
a, b = b, a
a


Out[11]:
5

另一种元组拆包,调用函数时,参数前面加个星号


In [14]:
divmod(20, 8) #a 除 b,返回商和余数


Out[14]:
(2, 4)

In [16]:
t = (20, 8)
divmod(*t)


Out[16]:
(2, 4)

In [17]:
quotient, remainder = divmod(*t)
quotient, remainder


Out[17]:
(2, 4)

上面也是一种元组拆包方法,调用函数时候用 * 把可迭代对象拆开作为函数参数。

下面的例子是让一个函数可以使用元组的方式返回多个值,os.path.split() 函数返回一个以路径和最后一个文件名组成的元组(path, last_part)


In [18]:
import os
_, filename = os.path.split('/home/kaka/.ssh/idrsa.pub')
filename


Out[18]:
'idrsa.pub'

如果在编写国际化的软件, 并不是一个很好的占位符,因为传统上,它会被当成 gettext.gettext 函数的别名,其他时候, 是一个好的占位符

使用 * 来处理剩下的元素

以 *args 来指定函数参数,可以获取任意数量的参数,这是 Python 中一种经典的写法

Python 3 中,这个概念被扩展到平行赋值中。


In [19]:
a, b, *rest = range(5)
a, b, rest


Out[19]:
(0, 1, [2, 3, 4])

In [20]:
a, b, *rest = range(3)
a, b, rest


Out[20]:
(0, 1, [2])

In [21]:
a, b, *rest = range(2)
a, b, rest


Out[21]:
(0, 1, [])

在平行赋值情况下, * 前置符只能用在一个变量前面,但是可以这个变量可以是任意位置


In [23]:
a, *body, c, d = range(5)
a, body, c, d


Out[23]:
(0, [1, 2], 3, 4)

In [24]:
*head, b, c, d = range(5)
head, b, c, d


Out[24]:
([0, 1], 2, 3, 4)

嵌套元组拆包

我们可以在需要用运算式来拆解的 tuple 中套入其它的 tuple,例如(a, b, (c, d)),而且如果运算式与嵌套结构匹配,python 就会做出正确的动作


In [2]:
metro_areas = [
    ('Tokyo', 'JP', 36.933, (35.689722, 139.691667)),
    ('Delhi NCR', 'IN', 21.935, (28.613889, 77.208889)),
    ('Mexico City', 'MX', 20.142, (19.43333, -99.13333)),
    ('Net York-Newark', 'US', 20.104, (40.808611, -74.020386)),
    ('Sao Paulo', 'BR', 19.649, (-23.547778, -46.635883))
]

print('{:15} | {:^9} | {:^9}'.format('', 'lat.', 'long.')) #^9 表示占 9 个字符,居中显示
fmt = '{:15} | {:^9} | {:^9}'
for name, cc, pop, (latitude, longitude) in metro_areas:
    if longitude <= 0: #西半球
        print(fmt.format(name, latitude, longitude))


                |   lat.    |   long.  
Mexico City     | 19.43333  | -99.13333
Net York-Newark | 40.808611 | -74.020386
Sao Paulo       | -23.547778 | -46.635883

在 python3 之前,你可以在定义函数时,可以使用嵌套元组做形参,例如 def func(a, (b, c), d)。但是 python3 已经不支持了,不过这对函数调用者没有任何影响,它改变的是某些函数的声明方式

按照设计,tuple 是非常方便的,可以把它当成记录来使用,不过它不可以指定名字,这也是 namedtuple 被设计出来的原因

可以指定名字的元组(具名元组)

collections.namedtuple 是一个工厂函数,可以构建一个带字段名的元组和一个有名字的类。这个带名字的类对调试程序有很大的帮助。使用 namedtuple 创建的实例消耗的内存与元组是一样的,因为字段名都被存储到相应的类中。这个实例跟普通的对象实例比起来要小一些,因为 Python 不会用到 __dict__ 来存放这些实例的属性

下面展示了如何用具名元组来记录一个城市的信息。


In [27]:
from collections import namedtuple
City = namedtuple('City', 'name country population coordinates')
tokyo = City('Tokyo', 'JP', 36.933, (35.689722, 139.691667))
tokyo


Out[27]:
City(name='Tokyo', country='JP', population=36.933, coordinates=(35.689722, 139.691667))

In [28]:
tokyo.coordinates


Out[28]:
(35.689722, 139.691667)

In [29]:
tokyo[1]


Out[29]:
'JP'

构建 namedtuple 需要两个参数,第一个是类名,另一个是各个字段的名字,可以使用可迭代的字符串(列表),也可以使用用空格分隔的字符串。

存放在对应字段里的数据要以一串参数的形式传入到构造函数中。注意,元祖的构造函数值接受单一的可迭代对象。

可以用字段名或位置来读取数据

下面是 namedtuple 的常用属性:


In [31]:
City._fields #是 1 个元组,里面是我们的字段名


Out[31]:
('name', 'country', 'population', 'coordinates')

In [33]:
LatLong = namedtuple('LatLong', 'lat long')
delhi_data = ('Delhi NCR', 'IN', 21.935, LatLong(28.613889, 77.208889))
delhi = City._make(delhi_data) #_make() 可以让我们接受一个可迭代对象生成这个类的一个实例。City(*delhi_data) 也可以做这件事
delhi._asdict() #把具名元组按照 collections.OrderedDict 的形式返回, 用来友好的显示城市资料


Out[33]:
OrderedDict([('name', 'Delhi NCR'),
             ('country', 'IN'),
             ('population', 21.935),
             ('coordinates', LatLong(lat=28.613889, long=77.208889))])

Tuple 当成不可变列表使用

当 tuple 当成不可变列表使用时,我们先看总结一下它和列表多么相似。tuple 支持所有不涉及添加或移除元素的方法,除了一个例外: 元组没有 __reversed__ 方法,但是这个方法只是一个优化而已,没有它,也可以使用 reversed(my_tuple)