Keep It Simple.Stupid.

Don Fisher's Blog

《流畅的python》之序列模型一

一、Python序列的分类

        python中内置序列类型可以划分为两种分类方式。

        第一种分类方式,按存放值还是存放值的引用来划分可以分为“容器序列”和“扁平序列”,其中“容器序列”存放的是任意类型的对象的引用,而“扁平序列”存放的是值而不是引用。

容器序列:

        list,tuple,collection.deque,可以存放不同类型的数据

扁平序列:

         str,bytes,bytearray,memoryview,array.array

        第二种分类方式,按可变不可变来划分。

可变序列:

        list,bytearray,array.array,collection.deque,memoryview

不可变序列:

        tuple,str,bytes 

二、列表推导和生成器表达式

        列表推导式在python中是非常常用的了,它的唯一作用就是生成列表。它有很好的可读性,并且非常简短。但是,正是因为列表推导的短小精悍,有可能造成滥用列表推导的情况。如果列表推导的代码长度超过两行,最好考虑用for循环重写。

        在Python2.x中,列表推导变量存在泄露问题:

x = 'aaa'
dummy  = [for x in range(3)]
print x #2

        可以看到x的值被重写了,而在Python3中,变量泄露的问题不会再有。

        列表推导可以帮我们把一个序列或者其它可迭代类型中的元素过滤或者加工,组成一个新的列表。Python中filter和map函数组合起来也能达到这一效果,他们之间有什么区别呢?   

#使用列表推导
symbols = 'ABCDEFG'

beyond_ascii = [ord(x) for s in symbols if ord(x) > 68]

print(beyond_ascii)

#使用fitter/map

symbols = 'ABCDEFG'

beyond_ascii = list(filter(lambda x : x > 68,map(ord,symbols)))

print(beyond_ascii)

        虽然使用filter/map也能达到相同的效果,但是使用filter/map的可读性,就不如列表推导式了,而且效率上,filter/map也不见得比列表生成式快(至少在上面的例子中不一定)。

        生成器表达式就是在列表推导的基础上,将 [ ] 替换为 ( ) ,它与列表推导的区别在于,生成器表达式是逐个产生元素,并不是一次性将所有元素生成。对于大量数据或者无限的序列来说(列如斐波那契数列),可以避免额外内存的占用。当然,后面的异步IO和协程,将会着重描述生成器的工作原理。

三、元组不仅仅是不可变列表

        很多时候,python入门教科书上,会把元组成为“不可变列表”,然而并没有完全概括元组的特点。除了是不可变的列表之外,它还可以用于没有字段名的记录。接下来我们分别来看看元组用于记录和用于不可变列表

        元组和记录:

#记录经纬度
lax_coordinates = (33.9425-118.208056#记录东京的一些信息
city,year,pop,shg,area = ('Tokyo',2003,32450,0.66,8014)
#记录航班信息
traveler_ids = [('USA','31195855'),('BRA','CE342567')]
for passport in sorted(traveler_ids):
    print('%s/%s' % passport)
BRA/CE3423567
USA/31195855
for country,_ traveler_ids:
    print(country)

        for循环可以分别提取元组里的元素,这个操作叫元组拆包,元组拆包可以运用在任何可迭代对象上,唯一的要求是,可迭代对象的呀u元素数量必须要跟接受这些元素的元组空挡数一致,除非用*表示多余的元素。在进行拆包的时候,我们并不对所有数据都感兴趣,占位符_能帮助处理这种情况。

        在Python中,函数用*args来获取不确定数量的参数,而Python3将这个概念扩展到平行赋值之中:

        a , b , *rest = range(5)

        a , b , rest

        (0,1,[2,3,4])

        *前缀只能用在一个变量前面,但是这个变量可以在赋值表达式的任意位置:

        *rest , a , b = range(5)

        rest , a , b 

        ([0,1,2],3,4)                        

        作为记录来说,元组已经很好用了,但是,要给记录中的字段命名,那就更好用了,nametuple帮我们解决这个问题。

        具名元组(collection.nametuple),它可以用来构建一个带字段名的元组和一个有名字的类。

        注意:

                1.用nametuple构建的类的实例消耗的内存跟元组是一样的。

                2.创建一个具名元组需要两个参数。一个是类名,另一个是类的各个字段的名字,后者可以是由数个字符串组成的可迭代对象,也可以是由空格分开的字符串。

                3.存在对应字段里的数据要以一串参数的形式传入到构造函数中

                4.可以通过字段名或者位置来获取一个字段信息

        例如:

from collection import namedtuple
City = namedtuple('City','name country population coordinates')
tokyo = City('Tokyo','JP',36.933,(35.689722,139.691667))
print(tokyo.population)#36.933
print(tokyo[1])#JP

        具名元组的属性和方法:

        _fields属性返回这个类所有字段名称的元组

        _make()方法通过接受一个可迭代对象来生成一个实例

        _asdict()方法把具名元组以collections.OrderDict的形式返回,可以把元组信息友好的展现出来

        元组作为不可变列表

        对比可变序列list,元组是不可变列表,既然是不可变列表,list中的增删改方法,对于tuple都是不适用的。

四、序列的切片

        对于list , tuple , str这类序列,都支持切片操作,Python程序员都知道s[a:b]形式的切片,但是切片操作比想象中要强大。

        对对象进行切片:

        s[a,b,c]是切片操作的形式,a是起始位置,b是结束位置,c是步长,c可以为负值,意味着反向取值。 

        对切片赋值?

        切片操作一般是在赋值语句的右边,但是如果把切片操作放到左边呢?

        没错,这样就可以对序列进行嫁接,切除或者就地修改了。

l = list(range(10))
print(l) # [0,1,2,3,4,5,6,7,8,9]
l[2:5] = [20,30]
print(l) # [0,1,20,30,5,6,7,8,9]
del l[5:7] 
print(l) # [0,1,20,30,5,8,9]
l[2:5] = [100]
print(l) #[0,1,100,8,9]


标签: :

0 条评论

想说点什么呢