本笔记是准备python简单的学习所记录的,大概只有注意事项与一些语法
大部分摘录自廖雪峰网站
基础知识
查看帮助
在交互命令模式(python的命令行),使用help(函数名称)
产看某个函数的帮助信息.
最简单的python演示
演示一个for循环
1 | sum=0 |
定义函数
1 | def my_add(x): |
- python没有语句结束符号,换行代表一个语句结束
- 使用冒号结尾时缩进的语句代表代码块,也就是java的花括号
- 删除缩进代表代码块结束
- 函数定义时最前面要加上
def
细节内容
循环
for…in…
a in b
能够判断a
是否包含在b
里面,包含就返回ture
不包含就返回false
是布尔值.
1 | sum = 0 |
除了直接列出list
也可以使用range()
函数,它会从0开始生成一个整数list
,比如range(5)
生成的序列是从0开始小于5的整数:
1 | >>> list(range(5)) |
while
1 | sum = 0 |
list
Python内置的一种数据类型是列表:list。list是一种有序的集合,可以随时添加和删除其中的元素。和数组相似
比如,列出班里所有同学的名字,就可以用一个list表示:
1 | >>> classmates = ['Michael', 'Bob', 'Tracy'] |
变量classmates
就是一个list。用len()
函数可以获得list元素的个数:
1 | >>> len(classmates) |
用索引来访问list中每一个位置的元素,记得索引是从0开始的:
1 | >>> classmates[0] |
list里面的元素的数据类型也可以不同,比如:
1 | >>> L = ['Apple', 123, True] |
list元素也可以是另一个list,比如:
1 | >>> s = ['python', 'java', ['asp', 'php'], 'scheme'] |
下面要拿到'php'
可以写p[1]
或者s[2][1]
,因此s
可以看成是一个二维数组
1 | >>> p = ['asp', 'php'] |
添加元素
直接添加到末尾
list是一个可变的有序表,所以,可以往list中追加元素到末尾:
1 | >>> classmates.append('Adam') |
添加到指定位置
也可以把元素插入到指定的位置,比如索引号为1
的位置:
1 | >>> classmates.insert(1, 'Jack') |
删除元素
要删除list末尾的元素,用pop()
方法:
1 | >>> classmates.pop() |
要删除指定位置的元素,用pop(i)
方法,其中i
是索引位置:
1 | >>> classmates.pop(1) |
替换元素
替换元素直接赋值
1 | >>> classmates[1] = 'Sarah' |
tuple
另一种有序列表叫元组:tuple。tuple和list非常类似,但是tuple一旦初始化就不能修改
1 | >>> classmates = ('Michael', 'Bob', 'Tracy') |
当你定义一个tuple时,在定义的时候,tuple的元素就必须被确定下来.如果要定义一个空的tuple,可以写成()
但是,要定义一个只有1个元素的tuple,如果你这么定义:
1 | >>> t = (1) |
定义的不是tuple,是1
这个数!这是因为括号()
既可以表示tuple,又可以表示数学公式中的小括号,这就产生了歧义,因此,Python规定,这种情况下,按小括号进行计算,计算结果自然是1
。
所以,只有1个元素的tuple定义时必须加一个逗号,
,来消除歧义:
1 | >>> t = (1,) |
Python在显示只有1个元素的tuple时,也会加一个逗号,
,以免你误解成数学计算意义上的括号。
最后来看一个“可变的”tuple:
1 | >>> t = ('a', 'b', ['A', 'B']) |
因为tuple的每个元素类型可以不同,而且允许list之类的作为原始.要创建一个内容也不变的tuple怎么做?那就必须保证tuple的每一个元素本身也不能变
dict
dict 内置字典,可以设置键值对的列表,键值不能重复
例如
1 | #names=['a':1,'b':2,'c':3,'d':4] |
也可给初值之后赋值
1 | #names['a']=2 |
可以用in
判断key是否存在
1 | # 'haha' in names |
也可以使用dict提供的get()
方法,如果key不存在,可以返回None
,或者自己指定的value:
1 | >>> d.get('Thomas') |
注意:返回None
的时候Python的交互环境不显示结果。
要删除一个key,用pop(key)
方法,对应的value也会从dict中删除:
1 | >>> d.pop('Bob') |
和list比较,dict有以下几个特点:
- 查找和插入的速度极快,不会随着key的增加而变慢;
- 需要占用大量的内存,内存浪费多。
而list相反:
- 查找和插入的时间随着元素的增加而增加;
- 占用空间小,浪费内存很少。
所以,dict是用空间来换取时间的一种方法。
dict可以用在需要高速查找的很多地方,在Python代码中几乎无处不在,正确使用dict非常重要,需要牢记的第一条就是dict的key必须是不可变对象。在Python中,字符串、整数等都是不可变的,因此,可以放心地作为key。而list是可变的,就不能作为key.
set
set和dict类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在set中,没有重复的key。
要创建一个set,需要提供一个list作为输入集合:
1 | >>> s = set([1, 2, 3]) |
注意,传入的参数[1, 2, 3]
是一个list,而显示的{1, 2, 3}
只是告诉你这个set内部有1,2,3这3个元素,显示的顺序也不表示set是有序的。。
重复元素在set中自动被过滤:
1 | >>> s = set([1, 1, 2, 2, 3, 3]) |
通过add(key)
方法可以添加元素到set中,可以重复添加,但不会有效果:
1 | >>> s.add(4) |
通过remove(key)
方法可以删除元素:
1 | >>> s.remove(4) |
set可以看成数学意义上的无序和无重复元素的集合,因此,两个set可以做数学意义上的交集、并集等操作:
1 | >>> s1 = set([1, 2, 3]) |
set和dict的唯一区别仅在于没有存储对应的value,但是,set的原理和dict一样,所以,同样不可以放入可变对象,因为无法判断两个可变对象是否相等,也就无法保证set内部“不会有重复元素”。
不可变对象
上面我们讲了,str (就是字符串)是不变对象,而list是可变对象。
对于可变对象,比如list,对list进行操作,list内部的内容是会变化的,比如:
1 | >>> a = ['c', 'b', 'a'] |
而对于不可变对象,比如str,对str进行操作呢:
1 | >>> a = 'abc' |
虽然字符串有个replace()
方法,也确实变出了'Abc'
,但变量a
最后仍是'abc'
,应该怎么理解呢?
我们先把代码改成下面这样:
1 | >>> a = 'abc' |
要始终牢记的是,a
是变量,而'abc'
才是字符串对象!有些时候,我们经常说,对象a
的内容是'abc'
,但其实是指,a
本身是一个变量,它指向的对象的内容才是'abc'
a
可以被重新赋值成ABC
但是abc
这个对象没有变,只是a
不再指向abc
这个对象了.
函数
定义函数
在Python中,定义一个函数要使用def
语句,依次写出函数名、括号、括号中的参数和冒号:
,然后,在缩进块中编写函数体,函数的返回值用return
语句返回。
如果想定义一个什么事也不做的空函数,可以用pass
语句:
1 | def nop(): |
缺少了pass
,代码运行就会有语法错误。
数据类型检查可以用内置函数isinstance()
实现:
1 | def my_abs(x): |
添加了参数检查后,如果传入错误的参数类型,函数就可以抛出一个错误:
1 | >>> my_abs('A') |
返回多个值
函数可以返回多个值吗?答案是肯定的
1 | import math |
import math
语句表示导入math
包,并允许后续代码引用math
包里的sin
、cos
等函数。
然后,我们就可以同时获得返回值:
1 | >>> x, y = move(100, 100, 60, math.pi / 6) |
但其实这只是一种假象,Python函数返回的仍然是单一值:
1 | >>> r = move(100, 100, 60, math.pi / 6) |
原来返回值是一个tuple!但是,在语法上,返回一个tuple可以省略括号,而多个变量可以同时接收一个tuple,按位置赋给对应的值,所以,Python的函数返回多值其实就是返回一个tuple,但写起来更方便。
位置参数
我们先写一个计算x2的函数:
1 | def power(x): |
对于power(x)
函数,参数x
就是一个位置参数。
可变参数
在Python函数中,还可以定义可变参数。顾名思义,可变参数就是传入的参数个数是可变的,可以是1个、2个到任意个,还可以是0个。
我们以数学题为例子,给定一组数字a,b,c……,请计算a2 + b2 + c2 + ……。
要定义出这个函数,我们必须确定输入的参数。由于参数个数不确定,我们首先想到可以把a,b,c……作为一个list或tuple传进来,这样,函数可以定义如下:
1 | def calc(numbers): |
但是调用的时候,需要先组装出一个list或tuple:
1 | >>> calc([1, 2, 3]) |
如果利用可变参数,调用函数的方式可以简化成这样:
1 | >>> calc(1, 2, 3) |
所以,我们把函数的参数改为可变参数:
1 | def calc(*numbers): |
定义可变参数和定义一个list或tuple参数相比,仅仅在参数前面加了一个*
号。在函数内部,参数numbers
接收到的是一个tuple,因此,函数代码完全不变。但是,调用该函数时,可以传入任意个参数,包括0个参数:
1 | >>> calc(1, 2) |
如果已经有一个list或者tuple,要调用一个可变参数怎么办?可以这样做:
1 | >>> nums = [1, 2, 3] |
这种写法当然是可行的,问题是太繁琐,所以Python允许你在list或tuple前面加一个*
号,把list或tuple的元素变成可变参数传进去:
1 | >>> nums = [1, 2, 3] |
*nums
表示把nums
这个list的所有元素作为可变参数传进去。这种写法相当有用,而且很常见。
关键字参数
可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。请看示例:
1 | def person(name, age, **kw): |
函数person
除了必选参数name
和age
外,还接受关键字参数kw
。在调用该函数时,可以只传入必选参数:
1 | >>> person('Michael', 30) |
也可以传入任意个数的关键字参数:
1 | >>> person('Bob', 35, city='Beijing') |
关键字参数有什么用?它可以扩展函数的功能。比如,在person
函数里,我们保证能接收到name
和age
这两个参数,但是,如果调用者愿意提供更多的参数,我们也能收到。试想你正在做一个用户注册的功能,除了用户名和年龄是必填项外,其他都是可选项,利用关键字参数来定义这个函数就能满足注册的需求。
和可变参数类似,也可以先组装出一个dict,然后,把该dict转换为关键字参数传进去:
1 | >>> extra = {'city': 'Beijing', 'job': 'Engineer'} |
当然,上面复杂的调用可以用简化的写法:
1 | >>> extra = {'city': 'Beijing', 'job': 'Engineer'} |
**extra
表示把extra
这个dict的所有key-value用关键字参数传入到函数的**kw
参数,kw
将获得一个dict,注意kw
获得的dict是extra
的一份拷贝,对kw
的改动不会影响到函数外的extra
。
命名关键字参数
对于关键字参数,函数的调用者可以传入任意不受限制的关键字参数。至于到底传入了哪些,就需要在函数内部通过kw
检查。
仍以person()
函数为例,我们希望检查是否有city
和job
参数:
1 | def person(name, age, **kw): |
但是调用者仍可以传入不受限制的关键字参数:
1 | >>> person('Jack', 24, city='Beijing', addr='Chaoyang', zipcode=123456) |
如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收city
和job
作为关键字参数。这种方式定义的函数如下:
1 | def person(name, age, *, city, job): |
和关键字参数**kw
不同,命名关键字参数需要一个特殊分隔符*
,*
后面的参数被视为命名关键字参数。
调用方式如下:
1 | >>> person('Jack', 24, city='Beijing', job='Engineer') |
如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*
了:
1 | def person(name, age, *args, city, job): |
命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错:
1 | >>> person('Jack', 24, 'Beijing', 'Engineer') |
由于调用时缺少参数名city
和job
,Python解释器把这4个参数均视为位置参数,但person()
函数仅接受2个位置参数。
命名关键字参数可以有缺省值,从而简化调用:
1 | def person(name, age, *, city='Beijing', job): |
由于命名关键字参数city
具有默认值,调用时,可不传入city
参数:
1 | >>> person('Jack', 24, job='Engineer') |
使用命名关键字参数时,要特别注意,如果没有可变参数,就必须加一个*
作为特殊分隔符。如果缺少*
,Python解释器将无法识别位置参数和命名关键字参数:
1 | def person(name, age, city, job): |
参数组合
在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。
比如定义一个函数,包含上述若干种参数:
1 | def f1(a, b, c=0, *args, **kw): |
在函数调用的时候,Python解释器自动按照参数位置和参数名把对应的参数传进去。
1 | >>> f1(1, 2) |
最神奇的是通过一个tuple和dict,你也可以调用上述函数:
1 | >>> args = (1, 2, 3, 4) |
所以,对于任意函数,都可以通过类似func(*args, **kw)
的形式调用它,无论它的参数是如何定义的。
虽然可以组合多达5种参数,但不要同时使用太多的组合,否则函数接口的可理解性很差。
高级特性
切片
取一个list或tuple的部分元素是非常常见的操作。比如,一个list如下:
1 | >>> L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack'] |
取前3个元素,应该怎么做?
笨办法:
1 | >>> [L[0], L[1], L[2]] |
之所以是笨办法是因为扩展一下,取前N个元素就没辙了。
取前N个元素,也就是索引为0-(N-1)的元素,可以用循环:
1 | >>> r = [] |
对这种经常取指定索引范围的操作,用循环十分繁琐,因此,Python提供了切片(Slice)操作符,能大大简化这种操作。
对应上面的问题,取前3个元素,用一行代码就可以完成切片:
1 | >>> L[0:3] |
L[0:3]
表示,从索引0
开始取,直到索引3
为止,但不包括索引3
。即索引0
,1
,2
,正好是3个元素。
如果第一个索引是0
,还可以省略:
1 | >>> L[:3] |
也可以从索引1开始,取出2个元素出来:
1 | >>> L[1:3] |
类似的,既然Python支持L[-1]
取倒数第一个元素,那么它同样支持倒数切片,试试:
1 | >>> L[-2:] |
记住倒数第一个元素的索引是-1
。
切片操作十分有用。我们先创建一个0-99的数列:
1 | >>> L = list(range(100)) |
可以通过切片轻松取出某一段数列。比如前10个数:
1 | >>> L[:10] |
后10个数:
1 | >>> L[-10:] |
前11-20个数:
1 | >>> L[10:20] |
前10个数,每两个取一个:
1 | >>> L[:10:2] |
所有数,每5个取一个:
1 | >>> L[::5] |
甚至什么都不写,只写[:]
就可以原样复制一个list:
1 | >>> L[:] |
tuple也是一种list,唯一区别是tuple不可变。因此,tuple也可以用切片操作
字符串'xxx'
也可以看成是一种list,每个元素就是一个字符。因此,字符串也可以用切片操作,只是操作结果仍是字符串:
1 | >>> 'ABCDEFG'[:3] |
在很多编程语言中,针对字符串提供了很多各种截取函数(例如,substring),其实目的就是对字符串切片。Python没有针对字符串的截取函数,只需要切片一个操作就可以完成,非常简单。
迭代
如果给定一个list或tuple,我们可以通过for
循环来遍历这个list或tuple,这种遍历我们称为迭代(Iteration)。
在Python中,迭代是通过for ... in
来完成的,而很多语言比如C语言,迭代list是通过下标完成的,比如Java代码:
1 | for (i=0; i<list.length; i++) { |
可以看出,Python的for
循环抽象程度要高于C的for
循环,因为Python的for
循环不仅可以用在list或tuple上,还可以作用在其他可迭代对象上。
list这种数据类型虽然有下标,但很多其他数据类型是没有下标的,但是,只要是可迭代对象,无论有无下标,都可以迭代,比如dict就可以迭代:
1 | >>> d = {'a': 1, 'b': 2, 'c': 3} |
因为dict的存储不是按照list的方式顺序排列,所以,迭代出的结果顺序很可能不一样。
默认情况下,dict迭代的是key。如果要迭代value,可以用for value in d.values()
,如果要同时迭代key和value,可以用for k, v in d.items()
。
由于字符串也是可迭代对象,因此,也可以作用于for
循环:
1 | >>> for ch in 'ABC': |
所以,当我们使用for
循环时,只要作用于一个可迭代对象,for
循环就可以正常运行,而我们不太关心该对象究竟是list还是其他数据类型。
那么,如何判断一个对象是可迭代对象呢?方法是通过collections模块的Iterable类型判断:
1 | >>> from collections import Iterable |
最后一个小问题,如果要对list实现类似Java那样的下标循环怎么办?Python内置的enumerate
函数可以把一个list变成索引-元素对,这样就可以在for
循环中同时迭代索引和元素本身:
1 | >>> for i, value in enumerate(['A', 'B', 'C']): |
上面的for
循环里,同时引用了两个变量,在Python里是很常见的,比如下面的代码:
1 | >>> for x, y in [(1, 1), (2, 4), (3, 9)]: |
列表生成式
列表生成式即List Comprehensions,是Python内置的非常简单却强大的可以用来创建list的生成式。
举个例子,要生成list [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
可以用list(range(1, 11))
:
1 | >>> list(range(1, 11)) |
但如果要生成[1x1, 2x2, 3x3, ..., 10x10]
怎么做?方法一是循环:
1 | >>> L = [] |
但是循环太繁琐,而列表生成式则可以用一行语句代替循环生成上面的list:
1 | >>> [x * x for x in range(1, 11)] |
写列表生成式时,把要生成的元素x * x
放到前面,后面跟for
循环,就可以把list创建出来,十分有用,多写几次,很快就可以熟悉这种语法。
for循环后面还可以加上if判断,这样我们就可以筛选出仅偶数的平方:
1 | >>> [x * x for x in range(1, 11) if x % 2 == 0] |
还可以使用两层循环,可以生成全排列:
1 | >>> [m + n for m in 'ABC' for n in 'XYZ'] |
三层和三层以上的循环就很少用到了。
运用列表生成式,可以写出非常简洁的代码。例如,列出当前目录下的所有文件和目录名,可以通过一行代码实现:
1 | >>> import os # 导入os模块,模块的概念后面讲到 |
for
循环其实可以同时使用两个甚至多个变量,比如dict
的items()
可以同时迭代key和value:
1 | >>> d = {'x': 'A', 'y': 'B', 'z': 'C' } |
因此,列表生成式也可以使用两个变量来生成list:
1 | >>> d = {'x': 'A', 'y': 'B', 'z': 'C' } |
最后把一个list中所有的字符串变成小写:
1 | >>> L = ['Hello', 'World', 'IBM', 'Apple'] |
生成器
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]
改成()
,就创建了一个generator:
1 | >>> L = [x * x for x in range(10)] |
创建L
和g
的区别仅在于最外层的[]
和()
,L
是一个list,而g
是一个generator。
我们可以直接打印出list的每一个元素,但我们怎么打印出generator的每一个元素呢?
如果要一个一个打印出来,可以通过next()
函数获得generator的下一个返回值:
1 | >>> next(g) |
我们讲过,generator保存的是算法,每次调用next(g)
,就计算出g
的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration
的错误。
当然,上面这种不断调用next(g)
实在是太变态了,正确的方法是使用for
循环,因为generator也是可迭代对象:
1 | >>> g = (x * x for x in range(10)) |
所以,我们创建了一个generator后,基本上永远不会调用next()
,而是通过for
循环来迭代它,并且不需要关心StopIteration
的错误。
generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for
循环无法实现的时候,还可以用函数来实现。
比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:
1, 1, 2, 3, 5, 8, 13, 21, 34, …
斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:
1 | def fib(max): |
注意,赋值语句:
1 | a, b = b, a + b |
相当于:
1 | t = (b, a + b) # t是一个tuple |
但不必显式写出临时变量t就可以赋值。
上面的函数可以输出斐波那契数列的前N个数:
1 | >>> fib(6) |
仔细观察,可以看出,fib
函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。
也就是说,上面的函数和generator仅一步之遥。要把fib
函数变成generator,只需要把print(b)
改为yield b
就可以了:
1 | def fib(max): |
这就是定义generator的另一种方法。如果一个函数定义中包含yield
关键字,那么这个函数就不再是一个普通函数,而是一个generator:
1 | >>> f = fib(6) |
这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return
语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()
的时候执行,遇到yield
语句返回,再次执行时从上次返回的yield
语句处继续执行。
举个简单的例子,定义一个generator,依次返回数字1,3,5:
1 | def odd(): |
调用该generator时,首先要生成一个generator对象,然后用next()
函数不断获得下一个返回值:
1 | >>> o = odd() |
可以看到,odd
不是普通函数,而是generator,在执行过程中,遇到yield
就中断,下次又继续执行。执行3次yield
后,已经没有yield
可以执行了,所以,第4次调用next(o)
就报错。
回到fib
的例子,我们在循环过程中不断调用yield
,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。
同样的,把函数改成generator后,我们基本上从来不会用next()
来获取下一个返回值,而是直接使用for
循环来迭代:
1 | >>> for n in fib(6): |
但是用for
循环调用generator时,发现拿不到generator的return
语句的返回值。如果想要拿到返回值,必须捕获StopIteration
错误,返回值包含在StopIteration
的value
中:
1 | >>> g = fib(6) |
关于如何捕获错误,后面的错误处理还会详细讲解。
迭代器
我们已经知道,可以直接作用于for
循环的数据类型有以下几种:
一类是集合数据类型,如list
、tuple
、dict
、set
、str
等;
一类是generator
,包括生成器和带yield
的generator function。
这些可以直接作用于for
循环的对象统称为可迭代对象:Iterable
。
可以使用isinstance()
判断一个对象是否是Iterable
对象:
1 | >>> from collections import Iterable |
而生成器不但可以作用于for
循环,还可以被next()
函数不断调用并返回下一个值,直到最后抛出StopIteration
错误表示无法继续返回下一个值了。
可以被next()
函数调用并不断返回下一个值的对象称为迭代器:Iterator
。
可以使用isinstance()
判断一个对象是否是Iterator
对象:
1 | >>> from collections import Iterator |
生成器都是Iterator
对象,但list
、dict
、str
虽然是Iterable
,却不是Iterator
。
把list
、dict
、str
等Iterable
变成Iterator
可以使用iter()
函数:
1 | >>> isinstance(iter([]), Iterator) |
你可能会问,为什么list
、dict
、str
等数据类型不是Iterator
?
这是因为Python的Iterator
对象表示的是一个数据流,Iterator对象可以被next()
函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration
错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()
函数实现按需计算下一个数据,所以Iterator
的计算是惰性的,只有在需要返回下一个数据时它才会计算。
Iterator
甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。
小结
凡是可作用于for
循环的对象都是Iterable
类型;
凡是可作用于next()
函数的对象都是Iterator
类型,它们表示一个惰性计算的序列;
集合数据类型如list
、dict
、str
等是Iterable
但不是Iterator
,不过可以通过iter()
函数获得一个Iterator
对象。
Python的for
循环本质上就是通过不断调用next()
函数实现的,例如:
1 | for x in [1, 2, 3, 4, 5]: |
实际上完全等价于:
1 | # 首先获得Iterator对象: |
补充
with as 的用法
With语句是什么?
本条原作者:UlissesJr
链接:https://www.jianshu.com/p/c00df845323c
有一些任务,可能事先需要设置,事后做清理工作。对于这种场景,Python的with语句提供了一种非常方便的处理方式。一个很好的例子是文件处理,你需要获取一个文件句柄,从文件中读取数据,然后关闭文件句柄。
如果不用with语句,代码如下:
1 | file = open("/tmp/foo.txt") |
这里有两个问题。一是可能忘记关闭文件句柄;二是文件读取数据发生异常,没有进行任何处理。下面是处理异常的加强版本:
1 | file = open("/tmp/foo.txt") |
虽然这段代码运行良好,但是太冗长了。这时候就是with一展身手的时候了。除了有更优雅的语法,with还可以很好的处理上下文环境产生的异常。下面是with版本的代码:
1 | with open("/tmp/foo.txt") as file: |
with如何工作?
这看起来充满魔法,但不仅仅是魔法,Python对with的处理还很聪明。基本思想是with所求值的对象必须有一个enter()方法,一个exit()方法。
紧跟with后面的语句被求值后,返回对象的enter()方法被调用,这个方法的返回值将被赋值给as后面的变量。当with后面的代码块全部被执行完之后,将调用前面返回对象的exit()方法。
下面例子可以具体说明with如何工作:
1 | #!/usr/bin/env python |
正如你看到的,
- enter()方法被执行
- enter()方法返回的值 - 这个例子中是”Foo”,赋值给变量’sample’
- 执行代码块,打印变量”sample”的值为 “Foo”
- exit()方法被调用
with真正强大之处是它可以处理异常。可能你已经注意到Sample类的exit方法有三个参数- val, type 和 trace。 这些参数在异常处理中相当有用。我们来改一下代码,看看具体如何工作的。
1 | #!/usr/bin/env python |
这个例子中,with后面的get_sample()变成了Sample()。这没有任何关系,只要紧跟with后面的语句所返回的对象有enter()和exit()方法即可。此例中,Sample()的enter()方法返回新创建的Sample对象,并赋值给变量sample。
实际上,在with后面的代码块抛出任何异常时,exit()方法被执行。正如例子所示,异常抛出时,与之关联的type,value和stack trace传给exit()方法,因此抛出的ZeroDivisionError异常被打印出来了。开发库时,清理资源,关闭文件等等操作,都可以放在exit方法当中。
因此,Python的with语句是提供一个有效的机制,让代码更简练,同时在异常产生时,清理工作更简单。
简单的cookielib函数库(模块)
来自 python-44: cookielib的使用:https://my.oschina.net/u/2429887/blog/540152
查看这个模块的详细信息,使用 help
1 | import cookielib |
我们对cookies的简单操作有三个,获取,保存,取出
要完成这三件事情只需要这么模块里面的几个类:CookieJar,FileCookieJar,MozillaCookieJar,LWPCookieJar
它们之间的关系是这样的:CookieJar —-派生—->FileCookieJar —-派生—–>MozillaCookieJar和LWPCookieJar
1 | CookieJar____ |
CookieJar :获取cookies并保存到变量中
1 | import urllib2 |
将cookies保存到文件中
FileCookieJar(filename)
创建FileCookieJar实例,检索cookie信息并将信息存储到文件中,filename是文件名
MozillaCookieJar(filename)
创建与Mozilla cookies.txt文件兼容的FileCookieJar实例
LWPCookieJar(filename)
创建与libwww-perl Set-Cookie3文件兼容的FileCookieJar实例
至于Mozilla和libwww是什么,这里给了一个链接:[http://blog.csdn.net/heiyeshuwu/article/details/1691904](http://blog.csdn.net/heiyeshuwu/article/details/1691904)
简单的说就是两种不同的网页存取API
1 | import cookielib |
关于最后save方法的两个参数在此说明一下:
官方解释如下:
1 | ignore_discard: save even cookies set to be discarded. |
由此可见,ignore_discard的意思是即使cookies将被丢弃也将它保存下来,ignore_expires的意思是如果在该文件中 cookies已经存在,则覆盖原文件写入,在这里,我们将这两个全部设置为True
从文件中读取cookies
1 | import cookielib |
opener的使用
本条来自:https://my.oschina.net/u/2429887/blog/540172
上一小节讲了cookielib 的使用,不知道大家仔细看了没有,为了便于分析,我这里引用上一节的一段代码
1 | import urllib2 |
其实上一节的三段代码很好理解,只要你认真的去看然后找出他们的规律,这里我给大家简单的说一下
- 前两句是引入模块的操作,只要记住需要什么模块就行
- cookie = cookielib.CookieJar() 前面说了CookieJar用来保存cookies到变量中
- handler=urllib2.HTTPCookieProcessor(cookie)
opener = urllib2.build_opener(handler)
response = opener.open(‘http://www.baidu.com')这三句话每段程序里面都有
- 剩下来的就是对数据的各种操作,包括输出,保存,载入等等
1、2、4 都很容易,所以我们只要重点分析 3 就好,其实 3 也就是这一小节的重点, opener的使用
我们先来看带有网址的 response = opener.open('http://www.baidu.com')
,是不是跟我们前面的某段代码很像呢,现在我把这段代码贴出来看看response = urllib2.urlopen('http://www.baidu.com')
我们知道urllib2.urlopen
的作用是打开一个网址,那么类似的,opener.open
是不是也可以打开一个网址呢?我们可以编写代码来测试看看,但是在编写之前我又认真的看了这三句话,第一句我不知道是什么意思,暂且不管,第二句我本来也不知道的,但是我看到build_opener
这个单词,build不就是构建,建造的意思吗?而且 opener也是执行这一句之后得来的,这是不是说,要完成整个的功能,我至少需要用到这两句话,于是我测试了下面的代码:
1 | #!/uer/bin/env python |
OK,有输出,这说明了opener.open
就是打开一个网址的操作,但是urlopen
和opener.open
有什么区别呢?
- urlopen是opener.open中的一种,是最简单的opener.open
- 基本的urlopen()函数不支持验证、cookie或其他HTTP高级功能,要支持这些功能,必须使用opener
- build_opener()函数来创建自己的自定义Opener对象
结合我们最近讲的内容,我们来看看要创建一个可以处理cookies的opener应该怎么做呢
从图上我们可以看出,要给opener增加处理cookies的功能,我们需要使用到 HTTPCookieProcessor 的处理程序,我们试着来写一下
1 | opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookie)) |
我们再给程序变变形:
1 | handler=urllib2.HTTPCookieProcessor(cookie) |
这不就是前面说的3句话吗
有了这三句话的理解,我们再加上前面说的cookielib模块里面相应的类的功能,前面使用cookielib的例子就很好记下来了