Python复学笔记(二)——Python高级特性

Python高级特性

切片

作用:对list、string和tuple取部分元素

L[start: end: step] 表示对序列L,从start开始到end-1,共end-start个元素,每step个元素取一个

start:为空默认从头部元素开始

end:为空默认取a之后的所有元素

step:为空默认为1,取a到b-1的每个元素

同时:

1、支持倒数切片,序号同倒序索引。当step为负数时,表示从start坐标元素到end坐标元素逆序向前取。
同时,如果
step>0,start>end;
step<0,start<end
则返回空序列

>>> L=list(range(2,11))
>>> L
[2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> L[-2:]
[9, 10]
>>> L[-5::-1]       #当step为负数时,表示从start坐标元素到end坐标元素逆序向前取
[6, 5, 4, 3, 2]
>>> L[-5:-1:-1]     #step<0,start<end,返回空序列
[]

2、通过切片操作可以实现序列元素倒序的操作,如L[::-1]

>>> L[5:1:-1]
[7, 6, 5, 4]
>>> L[::-1]
[10, 9, 8, 7, 6, 5, 4, 3, 2]

3、可以直接通过切片直接访问并修改原序列中的元素,该操作同索引

>>> seq = [7, 2, 3, 7, 5, 6, 0, 1]
>>> seq[3:4]=[6,3]
>>> seq
[7, 2, 3, 6, 3, 5, 6, 0, 1]

迭代

Python中,迭代通过for循环遍历来实现,且其抽象程度高于C的for循环,因为Python的for循环不仅可以作用到listtuple上,还可以作用到其他可迭代对象上,无论有无下标。

例如,dict

>>> d = {'a': 1, 'b': 2, 'c': 3,'d':4}
>>> for key in d :
	print(key,end=' ')

	
a b c d 

默认情况下,dict迭代的是key。如果要迭代value,可以用for value in d.values(),如果要同时迭代key和value,可以用for k, v in d.items()

Python内置的enumerate() 函数可用于将一个可遍历的数据对象(序列、迭代器、或其他支持迭代对象)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中。

>>> l=['one','two','three','four']
>>> a=list(enumerate(l))
>>> a
[(0, 'one'), (1, 'two'), (2, 'three'), (3, 'four')]
>>> for num,string in enumerate(l):
	print(num,string)

	
0 one
1 two
2 three
3 four
可迭代对象Iterable

可迭代对象,如上文提到的listtupledictstring

通过isinstance()函数和collections.abc模块的Iterable类型判断:

>>> from collections.abc import Iterable
>>> isinstance([1,2,3,4],Iterable)
True
>>> isinstance('abc',Iterable)
True
>>> isinstance( {'a': 1, 'b': 2, 'c': 3,'d':4},Iterable)
True

推导式

列表推导式

列表推导式即List Comprehensions,是Python内置的非常简单却强大的可以用来创建list的生成式。

#[表达式 for 变量 in 列表 if 条件]
[out_exp_res for out_exp in input_list if condition]

例如计算30内可以被3整除的整数:

>>> m=[i for i in range(30) if i%3 == 0]
>>> m
[0, 3, 6, 9, 12, 15, 18, 21, 24, 27]

过滤掉长度小于或等于3的字符串列表,并将剩下的转换成大写字母:

>>> names = ['Bob','Tom','alice','Jerry','Wendy','Smith']
>>> new_names = [name.upper()for name in names if len(name)>3]
>>> new_names
['ALICE', 'JERRY', 'WENDY', 'SMITH']

推导式中还可以嵌套多层循环,嵌套列表推导式看起来有些复杂。列表推导式的for部分是根据嵌套的顺序,过滤条件还是放在最后。记住,for表达式的顺序是与嵌套for循环的顺序一样(而不是列表推导式的顺序)。通常嵌套推导式不会有三级以上嵌套,这样会大大降低代码的可读性。

>>> [m + n for m in 'ABC' for n in 'XYZ']
['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']

for循环其实可以同时使用两个甚至多个变量,比如dictitems()可以同时迭代key和value,因此,列表推导式也可以使用两个变量来生成list:

>>> d = {'x': 'A', 'y': 'B', 'z': 'C' }
>>> [k + '=' + v for k, v in d.items()]
['y=B', 'x=A', 'z=C']

列表推导式几乎所有的列表都可以使用,是Python最受喜爱的特性之一,能大大化简你的代码,善于利用这种写法,可以写出非常整洁的代码,例如列出当前目录下的所有文件和目录名,可以通过一行代码实现:

>>> import os # 导入os模块
>>> [d for d in os.listdir('.')] # os.listdir可以列出文件和目录
['.emacs.d', '.ssh', '.Trash', 'Adlm', 'Applications', 'Desktop', 'Documents', 'Downloads', 'Library', 'Movies', 'Music', 'Pictures', 'Public', 'VirtualBox VMs', 'Workspace', 'XCode']
集合、字典推导式

集合的推导式与列表很像,适用列表推导式的所有内容,只不过用的是尖括号,生成集合对象:

set_comp = {expr for value in collection if condition}

与列表推导式类似,集合与字典的推导也很方便,而且使代码的读写都很容易。假如我们只想要字符串的长度,用集合推导式的方法非常方便:

>>> unique_lengths = {len(x) for x in strings}
>>> unique_lengths
{1, 2, 3, 4, 6}

map函数可以进一步简化:

In [158]: set(map(len, strings))
Out[158]: {1, 2, 3, 4, 6}

字典推导式表达式:

{ key_expr: value_expr for value in collection if condition }

作为一个字典推导式的例子,我们可以创建一个字符串的查找映射表以确定它在列表中的位置:

>>> loc_mapping = {val : index for index, val in enumerate(strings)}
>>> loc_mapping
{'a': 0, 'as': 1, 'bat': 2, 'car': 3, 'dove': 4, 'python': 5}
元组推导式(生成器表达式)

元组推导式基本格式:

(expression for item in Sequence if conditional )

元组推导式和列表推导式的用法也完全相同,只是元组推导式是用 () 圆括号将各部分括起来,而列表推导式用的是中括号 [],另外元组推导式返回的结果是一个生成器对象,具体放在生成器中讲。

迭代器Iterator

上面我们讲了可迭代对象Iterable,凡是可以作用到for循环的对象都可以称为可迭代对象。

从字面来理解,迭代器指的就是支持迭代的容器,更确切的说,是支持迭代的容器类对象,这里的容器可以是list、tuple等这些Python提供的基础容器,也可以是自定义的容器类对象,只要该容器支持迭代即可。

能以一种一致的方式对序列进行迭代(比如列表中的对象或文件中的行)是Python的一个重要特点。这是通过一种叫做迭代器协议(iterator protocol,它是一种使对象可迭代的通用方式)的方式实现的,一个原生的使对象可迭代的方法。

迭代器类中必须实现以下 2 个方法:

  1. __next__(self):返回迭代器的下一个元素。
  2. __iter__(self):调用该方法返回一个迭代器(iterator)

另外,Python内置了next()iter()函数,也可以实现以上效果。

iter(obj[, sentinel])  #obj必须是一个可迭代的容器对象 sentinel作为可选参数要求obj必须是一个可调用对象

我们使用 iter() 函数,通过传入一个可迭代的容器对象,或是通过调用可迭代对象的__iter__()方法,我们可以获得一个迭代器,通过每次调用迭代器的__next__()方法或内置next()函数,返回迭代器下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误,通过此方法也可以达到遍历的作用,如下:

>>> s='abcd'
>>> s_iter=s.__iter__()   # 返回生成的s_iter含有__next__()的内置方法,是迭代器对象。
>>> s_iter.__next__()     # __next__()从头一次取出迭代器对象中的值。
'a'
>>> next(s_iter)          #next()取出下一个元素值
'b'
>>> next(s_iter)
'c'
>>> next(s_iter)
'd'
>>> next(s_iter)         # 如果迭代器对象中的值被取完之后依然继续取,就会报错。 
Traceback (most recent call last):
  File "<pyshell#7>", line 1, in <module>
    next(s_iter)
StopIteration

for循环的机制
第一步:首先会进行dic = dic.__iter__(),将可迭代对象转化为迭代器对象
第二步:进行dic.__next__()的调用,得到返回值给k,然后进行代码块的操作
第三步:循环第二步,直到出现StopIteration错误,对错误进行捕捉,退出循环。

>>> dic={'a':1,'b':2,'c':3}
>>> for i in dic:                     #for方法
	print(i)

	
a
b
c
>>> dic_iter=dic.__iter__()           #for内部迭代器机制,第一步
>>> print(dic_iter.__next__())        #第二步
a
>>> print(dic_iter.__next__())
b
>>> print(dic_iter.__next__())
c
>>> print(dic_iter.__next__())        #第三步
Traceback (most recent call last):
  File "<pyshell#16>", line 1, in <module>
    print(dic_iter.__next__())
StopIteration

判断迭代器也可以使用collections.abc模块,其中的Iterator类判断

>>> from  collections.abc  import Iterator
>>> isinstance(dic_iter,Iterator)
True

迭代器的特点

惰性计算:不管迭代器对象有多大,同一时刻只有一个数据存在,返回下一个元素值。

一次性取值:迭代器只能取一次值,取完之后会返回StopIteration错误,你可以理解为此时迭代器为空(并不是None),这意味着处于该序列的值只能取一次。我们可以为回到zip()函数的例子,zip返回的为一个迭代器对象,对返回的对象mapping再次遍历,会发现生成的是一个空序列。

生成器Generator

生成器是另一种生成序列的方法,生成器通过每次的迭代生成一个元素,而不是一次性全部生成。生成器本质上也是一种特殊的迭代器,其内部含有__next__()__iter__()方法。

试想这样一种场景,通过算法生成了个大体量的序列元素,包含上万个元素,这会占用很大的空间,而我们只需要其中几个元素,那么大量的空间被浪费,使用生成器就不会造成这样的后果,生成器并不创建完整的序列,而是一边循环一边计算。

定义一个生成器,需要用到yield关键字,yield可以暂停函数的运行,不同于return,其可以让函数处于暂停的状态而不是停止,函数仍处于运行状态且不执行代码。同时,yield可以返回值,这点类似于return,其生成的值就是生成器对象

下面举斐波那契数列的例子,斐波那契数列无法通过列表推导式直接得到:

>>>def fib(max):               #feibonacci函数,输出斐波那契数列的前n个数
    n, a, b = 0, 0, 1
    while n < max:
        print(b,end=' ')
        a, b = b, a + b
        n = n + 1
    return 'done'
>>>>>> fib(8)
1 1 2 3 5 8 13 21 'done'

该函数定义了斐波那契数列的推算规则,通过这种推算规则一次性生成前n项数据,这里如果把函数里的print(b,end=' ')改为yield b,该函数将变为generator函数。

>>>def fib(max):               #feibonacci生成器函数
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1
    return 'done'
>>> f=fib(8)                   #返回函数对应的生成器对象
>>> type(f)
<class 'generator'>
>>> next(f)                    #通过调用__next__()方法或内置next()函数返回值
1
>>> next(f)
1
>>> next(f)
2
>>> f.__next__()
3
>>> f.__next__()
5
>>> f.__next__()
8
>>> f.__next__()
13
>>> f.__next__()
21
>>> f.__next__()               #生成器中无元素时返回StopIteration异常
Traceback (most recent call last):
  File "<pyshell#25>", line 1, in <module>
    f.__next__()
StopIteration: done

以上过程,结合迭代器的知识不难理解其执行过程,不同于迭代器的是,生成器是在每次被调用时才会进行计算生成下一个元素值。

我们还可以通过for循环的方式输出生成器的值,这样可以一次性的输出生成器所有元素的值,但是这样无法获取return中的返回值,需要捕获StopIteration异常中的value值:

>>> for n in fib(5):
     print(n)

     
1
1
2
3
5
>>> g=fib(5)
>>> while True:
     try:
         x = next(g)
         print(x)
     except StopIteration as e:
         print(e.value)
         break

        
1
1
2
3
5
done

生成器有另一个更为简洁的写法,生成器表达式如上文元组推导式所说,语法同列表推导式,也可以作为函数参数取代列表表达式。

>>> g = (x ** 2 for x in range(10))
>>> g
<generator object <genexpr> at 0x000001C1E8338CC8>
>>> def _make_gen():    #等价于此函数
    for x in range(100):
        yield x ** 2

        
>>> g=_make_gen()
>>> sum(x**2 for x in range(10))    #生成器表达式作为参数
285
>>> dict((i,i**2) for i in range(5))
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

生成器有两个需要注意的问题:

1、生成器是特殊的迭代器,同样具有惰性计算和一次性取值的特点,同一个生成器对象只能取一次值,取完值之后,生成器为空,返回None

2、生成器函数每次调用都会返回一个生成器对象,该对象会从第一个元素开始返回。所以如果尝试使用以下方法,会出现问题:

>>> next(fib(5))
1
>>> next(fib(5))
1
>>> next(fib(5))
1
>>> next(fib(5))
1
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值