使用#注释
使用\连接
把\放在一行的结束位置,Python仍然将其解释为同一行
使用if、elif和else进行比较
- and、or、not
- 5
num1 = 10
num2 = 900
if 5<num1<100<num2<1000:
print(1)
elif num1<=5:
print(2)
elif num1 == 100 and num2 >500:
print(3)
else:
print(4)
- 什么是真值(True)
如果表达式的返回类型不是布尔会发生什么?
被认为是False的表达式:
- 布尔 False
- null类型 None
- 整型 0
- 浮点型 0.0
- 空字符串 ”
- 空列表 []
- 空元组 ()
- 空字典 {}
- 空集合 set()
剩下的都会被认为是True。
使用while进行循环
- 示例
# count=1<=5后到条件判断语句,进入while循环。
# count=6>5后回到条件判断语句,不满足条件,while循环结束。
count =1
while count <= 5:
print(count)
count += 1
- 使用break跳出循环 (for 也一样)
- 使用continue跳到循环开始(即跳到条件判断的语句)(for 也一样)
- 循环外使用else (for 也一样)
如果while循环征程结束(没有使用break跳出),程序将进入到可选的else段。
场景如下:
当你使用循环来遍历检查某一数据结构时,找到满足条件的解使用break跳出,不执行else部分代码段;
没有找到可行解后,循环结束,将执行else部分代码段;
使用for迭代
任何可迭代对象都可以作用于for循环,包括我们自定义的数据类型,只要符合迭代条件,就可以使用for循环。
使用 for 循环的时候其实也是调用iter函数使可迭代对象返回一个迭代器,再使用迭代器进行循环
可迭代对象 (Iterable):
1. 集合数据类型,如list、tuple、dict(关于Python2与Python3字典的迭代)、set、str等;
2. generator,包括生成器和带yield的generator function。(生成器即是可迭代对象又是迭代器)(关于生成器)
理清可迭代对象、迭代器两个概念的关系和区别
iter函数
其实系统内置的iter 函数只是调用对象的iter方法,这个方法按照协议会返回一个迭代器,使得for循环这样的语法结构能够进行下去。
可迭代对象
- 实现了iter 方法,就是可以迭代的
- 可以返回自身作为迭代器(自身本来就有next方法,所以返回自身即可),也可以返回其他一个迭代器对象(自身没有next方法,所以不能返回自身,需要返回新的有next方法的迭代器对象)
- 使用 for 循环的时候其实也是调用iter函数使可迭代对象返回一个迭代器,再使用迭代器进行循环
迭代器
- python2: 实现了 next 方法
- python3: 实现了 _next_ 方法
next函数
调用迭代器的 next 或_next_ 方法,一直到结束对象函数返回 StopIteration 异常
迭代器与可迭代对象联系
其实没有什么必要联系,一个对象可以同时既是可迭代对象又是迭代器,只要方法里有 next(python2) 或_next_ (python3) 又有 _iter_ 方法。惯用的做法是_iter_方法返回自己作为迭代器。
其它
- 迭代器是单向的
- 迭代器转化为list或tuple之后就已经迭代到达尾端,不可再继续用了
- generator是迭代器也是迭代对象
关于字典的迭代:
- 默认对键进行迭代
- 因为dict的存储不是按照list的方式顺序排列,所以,迭代出的结果顺序很可能不一样。
把一个list变成索引-元素对
Python内置的enumerate函数可以把一个list变成索引-元素对,这样就可以在for循环中同时迭代索引和元素本身:
for i, value in enumerate('ABC'):
print(i, value)
0 A
1 B
2 C
使用zip()并行迭代
zip()函数后得到的是迭代器
其它迭代方式
文件迭代方式
推导式
推导式是从一个或者多个迭代器快速简洁地创建数据结构的一种方法。它可以将循环和条件判断相结合,从而避免语法冗长的代码。会使用推导式有时可以说明你已经超过Python初学者的水平。
列表推导式
[ expression for item in iterable if condition ]
例子1:创建一个在1到5之间的奇数列表
[ x for x in range(1, 6) if x % 2 == 1 ]
例子2:创建一个在1到5之间的奇数二元组列表
[(x, y) for x in range(1, 6) if x % 2 == 1 for y in range(1, 5) if x % 2 == 1]
字典推导式
{ key_expression : value_expression for item in iterable if condition }
例子:对字符串的字母进行计数
{letter: word.count(letter) for letter in word if letter.isalpha()}
集合推导式
{expression for expression for item in iterable if condition}
例子:
{number/3 for number in range(1,101) if number % 3 == 0}
生成器推导式
元组是没有推导式的。()是生成器推导式,它返回的是一个生成器对象,生成器对象既是可迭代对象也是迭代器:
例子:
一个生成器只能运行一次。列表、集合、字符串和字典都存储在内存中,但是生成器仅在运行中产生值,不会被存下来,所以不能重新使用或者备份一个生成器。
例子:尝试解释下列现象
你既可以通过生成器推导式创建生成器,也可以使用生成器的函数。
迭代的过程需要改变迭代列表
例子1:
错误示范
# 此程序陷入死循环,因为words遇到一个长度大于8的word后,在words的后面继续插入一个长度大于8的'hahahahahahahaha',word一直在变化
words = ['cat', 'window', 'defenestrate']
for word in words:
if len(word)>8:
words.append('hahahahahahahaha')
正确示范
# 迭代对象从words转化为words的拷贝对象,二者并非同一个对象
words = ['cat', 'window', 'defenestrate']
for word in words[:]:
if len(word)>8:
words.append('hahahahahahahaha')
函数
样例
# 当调用含参数的函数时,这些参数的值会被复制给函数中的对应参数
def do_nothing(input_element):
# pass表明函数没有做任何事情
pass
# 如果函数不显式调用return函数,那么就会默认返回None
关于None
None是Python中一个特殊的值,虽然它不表示任何数据,但仍然具有重要的作用。虽然None作为布尔值和False是一样的,但是它和False有很多差别。
为了区分None和布尔值False (0, 0.0, {}, (), [], set()),使用Python的is操作符:
if thing is None:
print("It's nothing")
else:
print("It's something")
位置参数
位置参数即传入参数的值是按照顺序依次复制过去的,就是我们常见语言(C++等)的传参形式,主要为了下面其它方式的区分。
关键字参数
为了避免位置参数带来的混乱,调用参数时可以指定对应参数的名字,甚至可以采用与函数定义不同的顺序调用
指定默认参数值
默认参数值在函数被定义时已经计算出来,而不是在程序运行时。Python程序员经常犯的一个错误是把可变的数据类型(例如列表或者字典)当作默认参数值。
原因:函数在定义时,默认参数haha所制定的地址已经确定,而L是一个list,所以每次调用该函数时,如果改变了L的内容,则下次调用时,默认参数的内容就会改变
默认参数必须指向不变对象!!!
修改:
def haveatry(haha=None)
if haha is None:
haha = []
haha.append('=_=')
print(haha)
使用*收集位置参数
当参数被用在函数内部时,星号将一组可变数量的位置参数集合成参数值的元组。在下面的例子中args是传入函数print_args()的参数的元组:
def print_args(*args):
print('Positional argument tuple:', args)
print_args()
print(3, 2, 1, 'wait!', 'uh...')
例子:
使用**收集关键字参数
使用两个星号可以将参数收集到一个字典中,参数的名字是字典的键,对应参数的值是字典的值。
例子:在函数内部kargs是一个字典
如果你把带有*args和**kargs的位置参数混合起来,它们必须按照顺序出现。
文档字符串
一等公民:函数
Python中一切都是对象,包括数字、字符串、元组、列表、字典和函数。
例子一:
例子二:
同样可以把函数作为列表、元组、集合和字典的元素。函数名是不可变的,因此可以把函数用作字典的键。
内部函数
当需要在函数内部多次执行复杂的任务时,内部函数是非常有用的,从而避免了循环和代码的堆叠重复。
例子:
def knights(saying):
def inner(quote):
return "We are the knights who say: '%s'" % quote
return inner(saying)
闭包
一,定义
python中的闭包从表现形式上定义(解释)为:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure)。
下面举一个简单的例子来说明。
>>>def addx(x):
>>> def adder(y): return x + y
>>> return adder
>>> c = addx(8)
>>> type(c)
<type 'function'>
>>> c.__name__
'adder'
>>> c(10)
18
结合这段简单的代码和定义来说明闭包:
如果在一个内部函数里:adder(y)就是这个内部函数,
对在外部作用域(但不是在全局作用域)的变量进行引用:x就是被引用的变量,x在外部作用域addx里面,但不在全局作用域里,
则这个内部函数adder就是一个闭包。
再稍微讲究一点的解释:
闭包=函数块+定义函数时的环境
// adder就是函数块,x就是环境
// 当然这个环境可以有很多,不止一个简单的x
闭包中是不能修改外部作用域的局部变量的
# 从执行结果可以看出,虽然在闭包里面也定义了一个变量m,但是其不会改变外部函数中的局部变量m。
>>> def foo():
... m = 0
... def foo1():
... m = 1
... print m
...
... print m
... foo1()
... print m
...
>>> foo()
0
1
0
闭包中在赋值符号”=”的左面的变量被认为是闭包的局部变量
# 这段程序的本意是要通过在每次调用闭包函数时都对变量a进行递增的操作
def foo():
a = 1
def bar():
a = a + 1
return a
return bar
但是在实际使用时会出错!
>>> c = foo()
>>> print c()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in bar
UnboundLocalError: local variable 'a' referenced before assignment
原因如下:
在执行代码 c = foo()时,python会导入全部的闭包函数体bar()来分析其的局部变量,python规则指定所有在赋值语句左面的变量都是局部变量,则在闭包bar()中,变量a在赋值符号”=”的左面,被python认为是bar()中的局部变量。
再接下来执行print c()时,程序运行至a = a + 1时,因为先前已经把a归为bar()中的局部变量,所以python会在bar()中去找在赋值语句右面的a的值,结果找不到,就会报错。
解决方法:
- 只要将a设定为一个容器就可以了
def foo():
a = [1]
def bar():
a[0] = a[0] + 1
return a[0]
return bar
复制代码
- 在python3以后,在a = a + 1 之前,使用语句nonloacal a就可以了,该语句显式的指定a不是闭包的局部变量。
def foo():
a = 1
def bar():
nonlocal a
a = a + 1
return a
return bar
闭包中引用外部循环变量
Python中,当循环结束以后,循环体中的临时变量i不会销毁,而是继续存在于执行环境中。
此外,python的函数只有在执行时,才会去找函数体里的变量的值。
有问题代码:
# 可能有些人认为这段代码的执行结果应该是2,3,4.但是实际的结果是4,4,4
flist = []
for i in range(3):
def foo(x): print x + i
flist.append(foo)
for f in flist:
f(2)
这是因为当把函数加入flist列表里时,python还没有给i赋值,只知道i为上下文,i的具体值需要调用执行时才知道。
而当执行时,再去找i的值是什么,这时在第一个for循环结束以后,i的值是2,所以以上代码的执行结果是4,4,4。
闭包作用
- 当闭包执行完后,仍然能够保持住当前的运行环境,闭包的外部变量。
- 闭包可以根据外部作用域的局部变量来得到不同的结果,这有点像一种类似配置功能的作用,我们可以修改外部的变量,闭包根据这个变量展现出不同的功能。
匿名函数:lambda()函数
生成器
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
要创建一个generator,有很多种方法。
方法一
第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:
# 创建L和g的区别仅在于最外层的[]和(),L是一个list,而g是一个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>
我们可以直接打印出list的每一个元素,但我们怎么打印出generator的每一个元素呢?如果要一个一个打印出来,可以通过next()函数获得generator的下一个返回值:
>>> next(g)
0
>>> next(g)
1
>>> next(g)
4
>>> next(g)
9
>>> next(g)
16
>>> next(g)
25
>>> next(g)
36
>>> next(g)
49
>>> next(g)
64
>>> next(g)
81
>>> next(g)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
我们讲过,generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。
当然,上面这种不断调用next(g)实在是太变态了,正确的方法是使用for循环,因为generator也是可迭代对象:
>>> g = (x * x for x in range(10))
>>> for n in g:
... print(n)
...
0
1
4
9
16
25
36
49
64
81
所以,我们创建了一个generator后,基本上永远不会调用next(),而是通过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:
# here
print(b)
a, b = b, a + b
n = n + 1
return 'done'
上面的函数可以输出斐波那契数列的前N个数:
>>> fib(6)
1
1
2
3
5
8
'done'
仔细观察,可以看出,fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。
也就是说,上面的函数和generator仅一步之遥。要把fib函数变成generator,只需要把print(b)改为yield b就可以了:
def fib(max):
n, a, b = 0, 0, 1
while n < max:
# here
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)就报错。
回到fib的例子,我们在循环过程中不断调用yield,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。
同样的,把函数改成generator后,我们基本上从来不会用next()来获取下一个返回值,而是直接使用for循环来迭代:
>>> for n in fib(6):
... print(n)
...
1
1
2
3
5
8
但是用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中:
>>> g = fib(6)
>>> while True:
... try:
... x = next(g)
... print('g:', x)
... except StopIteration as e:
... print('Generator return value:', e.value)
... break
...
g: 1
g: 1
g: 2
g: 3
g: 5
g: 8
Generator return value: done
装饰器
有时你需要在不改变源代码的情况下修改已经存在的函数。常见的例子是增加一句调试声明,以查看传入的参数。
装饰器实质上一个函数。它把一个函数作为输入并返回另外一个函数。在装饰器中,通常使用下面这些Python技巧:
- *args和**kwargs
- 闭包
- 作为参数的函数
例子1
函数document_it()定义了一个装饰器,会实现如下功能:
- 打印输出函数的名字和参数的值
- 执行含有参数的函数
- 打印输出结果
- 返回修改后的函数
def document_it(func):
def new_function1(*args, **kwargs):
# 打印输出函数的名字和参数的值
print('Running function:', func.__name__)
print('Positional arguments:', args)
print('Keyword arguments:', kwargs)
# 执行含有参数的函数
result = func(*args, **kwargs)
print ('Result:', result)
# 返回函数值
return result
# 返回修改后的函数
return new_function1
def add_ints(a, b):
return a+b
cooler_add_ints = document_it(add_ints)
cooler_add_ints(3, 5)
上面代码等价于下列简化的写法:
# @document_it 等价于 add_ints=document_it(add_ints)
# @decorator(text) 等价于 add_ints=(decorator(text))(add_ints)
@document_it
def add_ints(a, b):
return a+b
add_ints(3. 5) #等价于cooler_add_ints(3, 5)
注意此时,add_ints._name_ 为new_function1,并不为add_ints
若想令add_ints.name为add_ints,有下列方法:
方法1:
def document_it(func):
def new_function1(*args, **kwargs):
# 打印输出函数的名字和参数的值
print('Running function:', func.__name__)
print('Positional arguments:', args)
print('Keyword arguments:', kwargs)
# 执行含有参数的函数
result = func(*args, **kwargs)
print ('Result:', result)
# 返回函数值
return result
# here
new_function1.__name__ = func.__name__
# 返回修改后的函数
return new_function1
方法2(简化写法):
# 一定要导入functools模块
import functools
def document_it(func):
#等价于new_function1.__name__ = func.__name__
@functools.wraps(func)
def new_function1(*args, **kwargs):
# 打印输出函数的名字和参数的值
print('Running function:', func.__name__)
print('Positional arguments:', args)
print('Keyword arguments:', kwargs)
# 执行含有参数的函数
result = func(*args, **kwargs)
print ('Result:', result)
# 返回函数值
return result
# 返回修改后的函数
return new_function1
例子2 区别多个装饰器时的执行顺序
def square_it(func):
def new_function2(*args, **kwargs):
result = func(*args, **kwargs)
return result*result
return new_function2
# 等价于 add_ints=document_it(square_it(add_ints))
# suqrae_it(add_ints) == new_function2
# add_ints = document_it(new_function2)
@document_it
@square_it
def add_ints(a, b):
return a+b
>>> add_ints(3, 5)
Running function: new_function2 #注意这里为new_function2
Positional argument: (3, 5)
Keyword arguments: {}
Result: 65
64
# 等价于 add_ints=square_it(document_it(add_ints))
# document_it(add_ints) == new_function1
# add_ints = square_it(new_function1)
@document_it
@square_it
def add_ints(a, b):
return a+b
>>> add_ints(3, 5)
Running function: add_ints
Positional argument: (3, 5)
Keyword arguments: {}
Result: 8
64
命名空间和作用域
一个名词在不同的使用情况下可能指代不同的事物。Python程序有各种各样的命名空间,它指的是在该程序段内一个特定的名称是独一无二的,它和其他同名的命名空间是无关的。
每一个函数定义自己的命名空间。如果在主程序(main)中定义一个变量x,在另外一个函数中也定义x变量,两者指代的是不同的变量。
你可以在一个函数内得到某个全局变量的值:
但是,如果想在函数中得到一个全局变量的值并且改变它, 会报错:
animal在=左边,被认为是函数中的局部变量
为了读取全局变量而不是函数中的局部变量,需要在变量前面显式地加关键字global
如果在函数中不声明关键字global,Python会使用局部命名空间,同时变量也是局部的。- Python提供了两个获取命名空间内容的函数:
- locals()返回一个局部命名空间内容的字典;
- globals()返回一个全局命名空间内容的字典;
-
名称中的__用法
以两个下划线__开头和结束的名称都是Python的保留用法。因此,在自定义的变量中不能使用它们。
- 一个函数function的名称是系统变量function._name_
- 一个函数function的文档字符串是function._doc_
- 主程序被赋值特殊的名字_main_
使用try和except处理错误
short_list = [1, 2, 3]
position = 5
try:
short_list[position]
# 无参数的except适用于任何异常类型
except:
print('Need a position between 0 and', len(short_list)-1, ' but got', position)
异常只会被某一个except捕获
# 输入5,输出Bad index:5
short_list = [1, 2, 3]
while True:
value = input('Position [q to quit]? ')
if value == 'q':
break
try:
position = int(value)
print(short_list[position])
except IndexError as err:
print('Bad index:', position)
except Exception as other:
print('Something else broke:', other)
# 输入5,输出Something else broke: list index out of range
short_list = [1, 2, 3]
while True:
value = input('Position [q to quit]? ')
if value == 'q':
break
try:
position = int(value)
print(short_list[position])
except Exception as other:
print('Something else broke:', other)
except IndexError as err:
print('Bad index:', position)
编写自己的异常
前面一节讨论了异常处理,但是其中讲到的所有异常(例如 IndexError)都是在Python或者它的标准库中提前定义好的。
根据自己的目的可以使用任意的异常类型,同时也可以自己定义异常类型,用来处理程序中可能会出现的特殊情况。
一个异常是一个类,即类Exception的一个子类。现在编写异常UppercaseException,在一个字符串中碰到大写字母会被抛出。
try:
except UppercaseException as exc: