Python3简明教程之5高级特性

Python3之高级特性

 

切片

基本概念

切片操作符列表名称后跟一个方括号

方括号中有一对可选的数字,并用冒号分割

注意这与你使用的索引操作符十分相似。

记住数字是可选的,而冒号是必须的

注意:也可以用相同的方法访问元组和字符串。

 

我们先创建一个0-99的数列:

>>> L = list(range(100))

>>> L

[0, 1, 2, 3, ..., 99]

可以通过切片轻松取出某一段数列。

前10个数: L[:10]

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

 

前11-20个数: L[10:20]

[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

 

后10个数: L[-10:]

[90, 91, 92, 93, 94, 95, 96, 97, 98, 99]

 

 

切片操作符中的数字

第一个数(冒号之前)表示切片开始的位置

第二个数(冒号之后)表示切片结束的位置

第三个数(冒号之后)表示切片间隔数

如果不指定第一个数,Python就从序列首开始。

如果没有指定第二个数,则Python会停止在序列尾。

注意,返回的序列从开始位置开始 ,刚好在结束位置之前结束

即开始位置是包含在序列切片中的,而结束位置被排斥在切片外。

左开有闭

例如:

列表:shoplist=[0,1,2,3,4,5,6,7,8]

shoplist[1:3]返回:

从位置1开始,包括位置2,但是停止在位置3的一个序列切片

因此返回一个含有两个项目的切片

shoplist[:]返回:

整个序列的拷贝。

shoplist[::3]返回:

位置3,位置6,位置9…的序列切片。

 

切片操作符中的负数

负数用在从序列尾开始计算的位置

shoplist[:-1]返回:

除了最后一个项目外包含所有项目的序列切片,

shoplist[::-1]返回:

倒序序列切片。

 

 

元组的切片操作

tuple也是一种list,唯一区别是tuple不可变。

因此,tuple也可以用切片操作,只是操作的结果仍是tuple:

>>> (0, 1, 2, 3, 4, 5)[:3]

(0, 1, 2)

 

字符串的切片操作

字符串'xxx'也可以看成是一种list,每个元素就是一个字符。

因此,字符串也可以用切片操作,只是操作结果仍是字符串

>>> 'ABCDEFG'[:3]

'ABC'

>>> 'ABCDEFG'[::2]

'ACEG'

在很多编程语言中:

针对字符串提供了很多各种截取函数(例如,substring),

其实目的就是对字符串切片。

Python没有针对字符串的截取函数,

只需要切片一个操作就可以完成,非常简单。

 

 

 

 

迭代(Iterable)

 

什么是迭代

 

如果给定一个list或tuple,

我们可以通过for循环来遍历这个list或tuple,

这种遍历我们称为迭代(Iteration)

 

在Python中,迭代是通过for ... in来完成的,

而很多语言比如Java语言,迭代list是通过下标完成的,

比如Java代码:

for (i=0; i<list.length; i++) {

    n = list[i];

}

可以看出,Python的for循环抽象程度要高于Java的for循环

因为Python的for循环不仅可以用在list或tuple上,

还可以作用在其他可迭代对象上。

 

Iterable & Iterrator

可以直接作用于for循环的对象统称为可迭代对象(Iterable)

可以被next()函数调用并不断返回下一个值的对象称为迭代器(Iterator)

所有的Iterable均可以通过内置函数iter()来转变为Iterator。

 

对迭代器来讲,有一个__next()就够了。

在你使用for 和 in 语句时,

程序就会自动调用即将被处理的对象的迭代器对象,

然后使用它的next__()方法,直到监测到一个StopIteration异常

 

字典的迭代

list这种数据类型虽然有下标,

但很多其他数据类型是没有下标的,

不过,只要是可迭代对象,无论有无下标,都可以迭代,

比如dict就可以迭代:

>>> d = {'a': 1, 'b': 2, 'c': 3}

>>> for key in d:

...     print(key)

...

因为dict的存储不是按照list的方式顺序排列,

所以,迭代出的结果顺序很可能不一样。

 

默认情况下,dict迭代的是key。

如果要迭代value,可以用for value in d.values():,

如果要同时迭代key和value,可以用for k, v in d.items()。

 

字符串的迭代

由于字符串也是可迭代对象,因此,也可以作用于for循环:

>>> for ch in 'ABC':

...     print(ch)

...

A

B

C

所以,当我们使用for循环时,只要作用于一个可迭代对象,

for循环就可以正常运行,

而我们不太关心该对象究竟是list还是其他数据类型。

 

判断可迭代对象

那么,如何判断一个对象是可迭代对象呢?

方法是通过collections模块的Iterable类型判断:

>>> from collections import Iterable

>>> isinstance('abc', Iterable) # str是否可迭代

True

>>> isinstance([1,2,3], Iterable) # list是否可迭代

True

>>> isinstance(123, Iterable) # 整数是否可迭代

False

 

enumerate函数

如果要对list实现类似Java那样的下标循环怎么办?

Python内置的enumerate函数可以把一个list变成索引-元素对,

这样就可以在for循环中同时迭代索引和元素本身:

>>> for i, value in enumerate(['A', 'B', 'C']):

...     print(i, value)

...

0 A

1 B

2 C

 

for循环中的两个变量

上面的for循环里,同时引用了两个变量,

在Python里是很常见的,比如下面的代码:

>>> for x, y in [(1, 1), (2, 4), (3, 9)]:

...     print(x, y)

...

1 1

2 4

3 9

 

 

 

列表生成式

列表生成式即List Comprehensions:

是Python内置的非常简单却强大的可以用来创建list的生成式

 

列表生成式案例1

要生成list [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]:

可以用list(range(1, 11)):

>>> list(range(1, 11))

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

但如果要生成[1x1, 2x2, 3x3, ..., 10x10]怎么做?

方法一是循环:

>>> L = []

>>> for x in range(1, 11):

...    L.append(x * x)

...

>>> L

[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

但是循环太繁琐,

列表生成式则可以用一行语句代替循环生成上面的list:

>>>[x * x for x in range(1, 11)]

[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

 

写列表生成式时,

把要生成的元素x * x放到前面后面跟for循环

就可以把list创建出来,简单、方便。

列表生成式案例2

for循环后面还可以加上if判断:

这样我们就可以筛选出仅偶数的平方:

>>>[x * x for x in range(1, 11) if x % 2 == 0]

[4, 16, 36, 64, 100]

 

还可以使用两层循环:

可以生成全排列:

>>>[m + n for m in 'ABC' for n in 'XYZ']

['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']

 

列表生成式案例3

运用列表生成式,可以写出非常简洁的代码。

例如,列出当前目录下的所有文件和目录名

可以通过一行代码实现:

# 注意:os.listdir可以列出文件和目录

>>> import os # 导入os模块,模块的概念后面讲到

>>> [d for d in os.listdir('.')]

 

列表生成式案例4

for循环其实可以同时使用两个甚至多个变量,

比如dict的items()可以同时迭代key和value:

>>>d = {'x': 'A', 'y': 'B', 'z': 'C' }

>>> for k, v in d.items():

...     print(k, '=', v)

...

y = B

x = A

z = C

因此,列表生成式也可以使用两个变量来生成list:

>>> d = {'x': 'A', 'y': 'B', 'z': 'C' }

>>> [k + '=' + v for k, v in d.items()]

['y=B', 'x=A', 'z=C']

 

列表生成式案例5

把一个list中所有的字符串变成小写:

>>>L = ['Hello', 'World', 'IBM', 'Apple']

>>>[s.lower() for s in L]

['hello', 'world', 'ibm', 'apple']

 

 

 

迭代器(Iterator)

可以直接作用于for循环的数据类型有以下几种:

一类是集合数据类型,如list、tuple、dict、set、str等;

一类是generator,包括生成器和带yield的generator function。

可迭代对象:Iterable

这些可以直接作用于for循环的对象统称为可迭代对象:Iterable

可以使用isinstance()判断一个对象是否是Iterable对象:

生成器不但可以作用于for循环,

还可以被next()函数不断调用并返回下一个值,

直到最后抛出StopIteration错误表示无法继续返回下一个值了。

迭代器:Iterator

可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator

可以使用isinstance()判断一个对象是否是Iterator对象,

list、dict、str虽然是Iterable,却不是Iterator。

 

iter()函数

把list、dict、str等Iterable变成Iterator可以使用iter()函数

>>> isinstance(iter('abc'), Iterator)

True

 

Iterator甚至可以表示一个无限大的数据流,例如全体自然数。

而使用list是永远不可能存储全体自然数的。

 

小结

凡是可作用于for循环的对象都是Iterable类型;

凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;

 

集合数据类型如list、dict、str等是Iterable,但不是Iterator,

不过可以通过iter()函数获得一个Iterator对象。

 

 

生成器(Generator)

生成器的概念

通过列表生成式,我们可以直接创建一个列表。

但是,受到内存限制,列表容量肯定是有限的

而且,创建一个包含100万个元素的列表,会占用很大的存储空间,

如果我们仅仅需要访问前面几个元素,

那后面绝大多数元素占用的空间都白白浪费了。

 

所以,如果列表元素可以按照某种算法推算出来

那我们是否可以在循环的过程中不断推算出后续的元素呢?

这样就不必创建完整的list,从而节省大量的空间。

在Python中,这种一边循环一边计算的机制,称为生成器:generator

 

生成器对象是一个迭代器:

但是它比迭代器对象多了一些方法,

它们包括send方法,throw方法和close方法

这些方法,主要是用于外部与生成器对象的交互。

 

创建生成器之一

要创建一个generator,有很多种方法。

第一种方法很简单,

只要把一个列表生成式的[]改成(),就创建了一个generator:

>>> L = [x * x for x in range(10)]  #列表生成式

>>> L

[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

>>> g = (x * x for x in range(10))  #生成器

>>> g

<generator object <genexpr> at 0x1022ef630>

创建L和g的区别仅在于最外层的[]和()

L是一个list,而g是一个generator。

:可以通过next()函数获得generator的下一个返回值,

指导抛出异常:“StopIteration”

>>> next(g)

0

>>> next(g) ,#以此类推

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

StopIteration

我们讲过,generator保存的是算法

每次调用next(g),就计算出g的下一个元素的值,

直到计算到最后一个元素,

没有更多的元素时,抛出StopIteration的错误。

 

创建生成器之二

可以通过for循环创建生成器,因为generator也是可迭代对象:

>>>g = (x * x for x in range(10))

>>> for n in g:

...     print(n)

...

 

所以,创建了一个generator后,

基本上是通过for循环来迭代它,

并且不需要关心StopIteration的错误。

 

创建生成器之三

generator非常强大。

如果推算的算法比较复杂,

用类似列表生成式的for循环无法实现的时候,

还可以用函数来实现。

 

比如,著名的斐波拉契数列(Fibonacci),

除第一个和第二个数外,任意一个数都可由前两个数相加得到:

1, 1, 2, 3, 5, 8, 13, 21, 34, ...

斐波拉契数列用列表生成式写不出来,

但是,用函数把它打印出来却很容易:

 

def fib(max):

    n, a, b = 0, 0, 1

    while n < max:

        print(b)

        a, b = b, a + b

        n = n + 1

    return 'done'

注意,赋值语句:

a, b = b, a + b

相当于:

t = (b, a + b)  # t是一个tuple

a = t[0]

b = t[1]

神器:不必显式写出临时变量就可以赋值。

 

上面的函数可以输出斐波那契数列的前N个数:

>>> fib(6)

仔细观察,可以看出,

fib函数实际上是定义了斐波拉契数列的推算规则,

可以从第一个元素开始,推算出后续任意的元素,

这种逻辑其实非常类似generator,但不是generator

 

 

把fib函数变成generator,只需要把print(b)改为yield b就可以了:

def fib(max):

    n, a, b = 0, 0, 1

    while n < max:

        yield b

        a, b = b, a + b

        n = n + 1

    return 'done'

这就是定义generator的另一种方法。

如果一个函数定义中包含yield关键字,

那么这个函数就不再是一个普通函数,而是一个generator

>>> f = fib(6)

>>> f

<generator object fib at 0x104feaaa0>

生成器的执行流程

最难理解的就是generator和函数的执行流程不一样。

函数是顺序执行,遇到return语句或者最后一行函数语句就返回。

而变成generator的函数,在每次调用next()的时候执行,

遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

 

举个简单的例子,定义一个generator,依次返回数字1,3,5:

def odd():

    print('step 1')

    yield 1

    print('step 2')

    yield(3)

    print('step 3')

    yield(5)

调用该generator时,

首先要生成一个generator对象,

然后用next()函数不断获得下一个返回值:

>>> o = odd()

>>> next(o)

step 1

1

>>> next(o)

step 2

3

>>> next(o)

step 3

5

>>> next(o)

Traceback (most recent call last):

  File "<stdin>", line 1, in <module>

StopIteration

可以看到,odd不是普通函数,而是generator,

在执行过程中,遇到yield就中断,下次又继续执行。

执行3次yield后,已经没有yield可以执行了,

所以,第4次调用next(o)就报错。

 

把函数改成generator后,基本上从来不会用next()来获取下一个返回值,

而是直接使用for循环来迭代:

>>> for n in fib(6):

...     print(n)

...

 

 

 

 

 

容器

 

容器(container)

容器是一种把多个元素组织在一起的数据结构,

容器中的元素可以逐个地迭代获取,

可以用in, not in关键字判断元素是否包含在容器中。

 

在Python中,常见的容器对象有:

  1. list, deque, ….
  2. set, frozensets, ….
  3. dict, defaultdict, OrderedDict, Counter, ….
  4. tuple, namedtuple, …
  5. str

容器比较容易理解,

因为可以把它看作是一个盒子,里面可以塞任何东西。

从技术角度来说,当它可以用来询问某个元素是否包含在其中时,

那么这个对象就可以认为是一个容器,

比如 list,dict, set,tuples都是容器对象。

 

容器是一系列元素的集合,

str、list、set、dict、file、sockets对象都可以看作是容器,

容器都可以被迭代(用在for,while等语句中),

因此他们被称为可迭代对象。

 

 

 

 

可迭代对象(iterable)

但凡是可以返回一个迭代器的对象都可称之为可迭代对象,

 

 

迭代器(iterator)

那么什么迭代器呢?

它是一个带状态的对象,能在你调用next()方法的时候返回容器中的下一个值,

任何实现了__iter____next__()方法的对象都是迭代器,

__iter__返回迭代器自身,

__next__返回容器中的下一个值,

如果容器中没有更多元素了,则抛出StopIteration异常,

至于它们到底是如何实现的这并不重要。

 

迭代器就是实现了工厂模式的对象,

它在每次询问要下一个值的时候给你返

 

生成器(generator)

生成器简介

生成器算得上是Python语言中最吸引人的特性之一,

生成器其实是一种特殊的迭代器,不过这种迭代器更加优雅。

它不需要再像上面的类一样写__iter__()和__next__()方法了,

只需要一个yiled关键字

生成器一定是迭代器(反之不成立),

因此任何生成器也是以一种懒加载的模式生成值。

 

它特殊的地方在于函数体中没有return关键字

函数的返回值是一个生成器对象,

只有显示或隐示地调用next的时候才会真正执行里面的代码。

 

生成器在Python中是一个非常强大的编程结构,

可以用更少地中间变量写流式代码,

相比其它容器对象它更能节省内存和CPU,

当然它可以用更少的代码来实现相似的功能。

yield案例

生成器对象是一个迭代器:

但是它比迭代器对象多了一些方法,

它们包括send方法,throw方法和close方法

这些方法,主要是用于外部与生成器对象的交互。

 

send方法有一个参数:

该参数指定的是上一次被挂起的yield语句的返回值

案例如下:

def MyGenerator():

    receive = yield 100

    print('extra' + str(receive))

    receive = yield 200

    print('extra' + str(receive))

    receive = yield 300

    print('extra' + str(receive))

 

gen = MyGenerator()

print(next(gen))  #也可以用:print(gen.send(None))

print("first")

print(gen.send(2))

print("second")

print(gen.send(3))

print("third")

----

100

first

extra2

200

second

extra3

300

third

----

注意,yield语句是最具魔力的表达式:

这里“yield 100”整体被视为一个表达式,

send的内容会作为这个表达式的值

随便左边用什么变量接收或者不接收,

总之yield就是send进来的那个值。

这个表达式变成send进来后的那个值之后,继续往下执行,

直到再次遇到yield,挂起。

 

如何启动生成器呢?

调用(gen.next()或gen.send(None))

 

上面代码的运行过程如下。

当调用gen.next()方法时:

python首先会执行MyGenerator方法的yield 1语句

由于是一个yield语句,因此方法的执行过程被挂起

next方法返回值为yield关键字后面表达式的值,即为100。

 

当调用gen.send(2)方法时:

python首先恢复MyGenerator方法的运行环境

同时,将表达式(yield 100)的返回值定义为send方法参数的值,即为2。

这样,接下来value=(yield 100)这一赋值语句会将value的值设为2。

继续运行会遇到yield value语句,MyGenerator方法再次被挂起

同时,send方法的返回值为yield关键字后面表达式的值,

也即value的值,为200。

 

当调用gen.send(3)方法时:

首先恢复MyGenerator方法的运行环境

同时,将表达式(yield value)的返回值定义为send方法参数的值,即为3。

这样,接下来value=(yield 200)这一赋值语句会将value的值置为3。

继续运行,MyGenerator方法执行完毕。

以此类推….

 

总的来说:

send方法和next方法唯一的区别,

在执行send方法会首先把上一次挂起的yield语句的返回值通过参数设定,

从而实现与生成器方法的交互

但是需要注意,在一个生成器对象没有执行next方法之前,

由于没有yield语句被挂起,所以执行send方法会报错。

当然,下面的代码是可以接受的:

gen = MyGenerator()  

print( gen.send(None) )

因为当send方法的参数为None时,它与next方法完全等价。

但是注意,虽然上面的代码可以接受,但是不规范。

所以,在调用send方法之前,还是先调用一次next方法为好

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

福优学苑@音视频+流媒体

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值