用pythonic方式来思考(2)

2018-04-28  本文已影响0人  小懒额
第5条 了解切割序列的方法

python 支持对序列进行切片操作,可以对内置的 list、str和bytes 进行切割,切割操作还延伸到实现了 __get_item____set_item__ 的 python 类上。
基本操作方法: somelist[start:end],切片时包括 start,不包括 end。
几种切片实例:

test = [1, 2, 3, 4, 5, 6, 7, 8]
print(test[0:])
print(test[:len(test)])
print(test[:])
print(test[3:])
print(test[4:5])
print(test[-1:])
print(test[-4:-7])
>>>
[1, 2, 3, 4, 5, 6, 7, 8]
[1, 2, 3, 4, 5, 6, 7, 8]
[1, 2, 3, 4, 5, 6, 7, 8]
[4, 5, 6, 7, 8]
[5]
[8]
[]

切片时,下标可以越界,但是访问列表下标对应的元素时,不能越界,否则报 IndexError。

print(test[:20])
>>>
[1, 2, 3, 4, 5, 6, 7, 8]

somelist[-0:] 为原列表的拷贝。

print(test[-0:])
>>>
[1, 2, 3, 4, 5, 6, 7, 8]

对原列表进行切割,产生的时一张新列表,在新列表上操作。不会修改原列表。

test = [1, 2, 3, 4, 5, 6, 7, 8]
test1 = (test[:])
test1[2] = 9
print(test1)
print(test)
>>>
[1, 2, 3, 4, 5, 6, 7, 8]
[1, 2, 9, 4, 5, 6, 7, 8]

在对列表进行切割时赋值,列表会根据新值的个数相应扩张或收缩。

test = [1, 2, 3, 4, 5, 6, 7, 8]
test[:2] = [9, 9, 9, 9]
print(test)
test[:5] = [1,2]
print(test)
>>>
[9, 9, 9, 9, 3, 4, 5, 6, 7, 8]
[1, 2, 4, 5, 6, 7, 8]
第6条:在单次切片操作时,不要同时指定 start、end 和 stride

列表的步进切割,

test = [1, 2, 3, 4, 5, 6, 7, 8]
print(test[::2])
>>>
[1, 3, 5, 7]

当步进值为 -1 时,可以实现序列的翻转效果,(书上说这里不能够对 utf8 的unicode实现翻转,测试了下好像可以实现)

test = [1, 2, 3, 4, 5, 6, 7, 8]
print(test[::-1])
>>>
[8, 7, 6, 5, 4, 3, 2, 1]
a = 'hello world'
print(a[::-1])
>>>
dlrow olleh
a = '你好'
print(a[::-1])
b = u'你好'
print(b[::-1])
c = a.encode('utf-8')
print(c[::-1])
d = c.decode('utf-8')
print(d)
>>>
好你
好你
b'\xbd\xa5\xe5\xa0\xbd\xe4'
你好

切割列表时,在一堆中括号里写上三个数字太过于拥挤,而且不利于阅读。
如果对内存要求不是特别严格,尽量在使用时先做步进切割,再做内存切割。

第7条:用列表推导式来取代 map 和 filter

如果用下面列表里面的每个元素的平方来构建一个新的列表,先看一下 map 的做法:

test = [1, 2, 3, 4, 5, 6, 7, 8]
squa = map(lambda x: x**2, test)
print list(squa)
>>>
[1, 4, 9, 16, 25, 36, 49, 64]

使用列表推导式是这样:

test = [1, 2, 3, 4, 5, 6, 7, 8]
squa = [x**2 for x in test]
print(squa)
>>>
[1, 4, 9, 16, 25, 36, 49, 64]

这时如果只对列表中的偶数的平方值构建列表,使用 map 的时候需要配合 filter 才能实现,

test = [1, 2, 3, 4, 5, 6, 7, 8]
squa = map(lambda x:x**2, filter(lambda x:x%2 ==0, test))
print list(squa)
>>>
[4, 16, 36, 64]

但是列表推导式来实现就是这样:

test = [1, 2, 3, 4, 5, 6, 7, 8]
squa = [x**2 for x in test if x%2 == 0]
print(squa)
>>>
[4, 16, 36, 64]

可以看出,列表推导式的写法和可阅读性都强很多。

第8条:不要使用含有两个以上的列表推导式

当使用列表推导的多重循环时,可以实现将二维列表转换成一维列表。如:

matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [x for row in matrix for x in row]
print(flat)
>>>
[1, 2, 3, 4, 5, 6, 7, 8, 9]

然而当使用列表推导的多重循环时,效果如下:

matrix = [[[1, 2, 3], [4, 5, 6], [7, 8, 9]]]
flat = [x for row in matrix for line in row for x in line]
print(flat)
>>>
[1, 2, 3, 4, 5, 6, 7, 8, 9]

这时候看起来没有很简洁,如果换成普通的循环语句,反而更清晰一点,

flat = []
matrix = [[[1, 2, 3], [4, 5, 6], [7, 8, 9]]]
for row in matrix:
    for line in row:
        flat.extend(line)  #这里使用 extend 而不是 append 可以减少一行
print(flat)
>>>
[1, 2, 3, 4, 5, 6, 7, 8, 9]

除了多重循环之外,列表推导式还包括多重条件,

matrix = [1, 2, 3, 4, 5, 6, 7, 8, 9]
flat = [x for x in matrix if x%2 == 0 if x >4]
print(flat)
>>>
[6, 8]

实际使用中尽量避免去使用多重循环或者多重条件,最好只使用两个循环,两个条件,或者一个循环一个条件。

第9条:用生成器表达式来改写数据量较大的列表推导

在使用列表推导式时,需用使用一块内存来存储生成的全新列表,当数据量比较大时,这消耗大量内存。
如:

value = [len(x) for x in open('/tmp/my_file.txt')]

当这个文件非常大时,就会出现内存消耗过多的问题。
这个时候就需要生成器表达式来解决这个问题。生成器表达式在运行时,不会立刻加载所有数据,而是每次循环时使用迭代器主次返回数据。
把实现列表推导的写法放在()中,就实现了生成器表达式。
如:

value_generator = (len(x) for x in open('/tmp/my_file.txt'))
print(value_generator )
>>>
<generator object <genexpr> at 0x02A5F0C0>

连锁生成器表达式,

matrix = [1, 2, 3, 4, 5, 6, 7, 8, 9]
it = (x for x in matrix)  # 这里的it作为生成器继续使用
print(it)
roots = ((x, x**5) for x in it)  # it在使用时会记录当前状态,使用后不能继续使用
print(roots)
print(next(roots))
print(next(roots))
print(next(roots))
>>>
(1, 1)
(2, 32)
(3, 243)
上一篇下一篇

猜你喜欢

热点阅读