△命令行模式和Python交互模式

在Windows开始菜单选择“命令提示符”,就进入到命令行模式,它的提示符类似C:\>;
在命令行模式下敲命令python,就看到类似如下的一堆文本输出,然后就进入到Python交互模式,它的提示符是>>>。
在Python交互模式下输入exit()并回车,就退出了Python交互模式,并回到命令行模式。


△Python文件名只能是英文字母、数字和下划线的组合。(但是实践证明中文也是可以的,但是不建议。)
print()会依次打印每个字符串,遇到逗号“,”会输出一个空格

△每一行都是一个语句,当语句以冒号:结尾时,缩进的语句视为代码块。
缩进有利有弊。好处是强迫你写出格式化的代码,但没有规定缩进是几个空格还是Tab。按照约定俗成的管理,应该始终坚持使用4个空格的缩进。
缩进的另一个好处是强迫你写出缩进较少的代码,你会倾向于把一段很长的代码拆分成若干函数,从而得到缩进较少的代码。
缩进的坏处就是“复制-粘贴”功能失效了,这是最坑爹的地方。当你重构代码时,粘贴过去的代码必须重新检查缩进是否正确。此外,IDE很难像格式化Java代码那样格式化Python代码。
最后,请务必注意,Python程序是大小写敏感的,如果写错了大小写,程序会报错。


△整数和浮点数在计算机内部存储的方式是不同的,整数运算永远是精确的(除法难道也是精确的?是的!无论整数做//(地板除)除法还是取余数%,结果永远是整数,所以,整数运算结果永远是精确的。而/除法计算结果是浮点数,即使是两个整数恰好整除,结果也是浮点数),而浮点数运算则可能会有四舍五入的误差。

注意:Python的整数没有大小限制;
Python的浮点数也没有大小限制,但是超出一定范围就直接表示为inf(无限大)。

△字符串是以单引号'或双引号"括起来的任意文本,请注意,''或""本身只是一种表示方式,不是字符串的一部分。如果'本身也是一个字符,那就可以用""括起来。如果字符串内部既包含'又包含"可以用转义字符\来标识(比如:'I\'m\"OK\"!'),也可以用三个单引号或者三个双引号来标识(比如:'''I'm "OK"!''')。
Python还允许用r''表示''内部的字符串默认不转义(比如:>>> print(r'\\\t\\') 打印结果为 \\\t\\)。
如果字符串内部有很多换行,用\n写在一行里不好阅读,为了简化,Python允许用'''多行内容'''的格式表示多行内容。(比如:
a='''line1
line2
line3'''
print(a)
打印结果为
line1
line2
line3)

△空值是Python里一个特殊的值,用None表示。None不能理解为0,因为0是有意义的,而None是一个特殊的空值。

△所谓常量就是不能变的变量,比如常用的数学常数π就是一个常量。在Python中,通常用全部大写的变量名表示常量:PI = 3.14159265359
但事实上PI仍然是一个变量,Python根本没有任何机制保证PI不会被改变,所以,用全部大写的变量名表示常量只是一个习惯上的用法,如果你一定要改变变量PI的值,也没人能拦住你。

△字符编码
ASCII编码(1个字节) → Unicode编码(2个字节) → UTF-8编码(可变字节)
现在计算机系统通用的字符编码工作方式:
在计算机内存中,统一使用Unicode编码,当需要保存到硬盘或者需要传输的时候,就转换为UTF-8编码。(内存中是Unicode,输出设备上就是UTF-8)

△Python的字符串
对于单个字符的编码,Python提供了ord()函数获取字符的整数表示,chr()函数把编码转换为对应的字符:
>>> ord('A')
65
>>> chr(66)
'B'

由于Python的字符串类型是str,在内存中以Unicode表示,一个字符对应若干个字节。如果要在网络上传输,或者保存到磁盘上,就需要把str变为以字节为单位的bytes。
Python对bytes类型的数据用带b前缀的单引号或双引号表示:
x = b'ABC'
要注意区分'ABC'和b'ABC',前者是str,后者虽然内容显示得和前者一样,但bytes的每个字符都只占用一个字节。

以Unicode表示的str通过encode()方法可以编码为指定的bytes,例如:
>>> 'ABC'.encode('ascii')
b'ABC'
>>> '中文'.encode('utf-8')
b'\xe4\xb8\xad\xe6\x96\x87'
反过来,如果我们从网络或磁盘上读取了字节流,那么读到的数据就是bytes。要把bytes变为str,就需要用decode()方法:
>>> b'ABC'.decode('ascii')
'ABC'
>>> b'\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8')
'中文'

len()函数计算的是str的字符数,如果换成bytes,len()函数就计算字节数:
>>> len('中文')
2
>>> len('中文'.encode('utf-8'))
6

△格式化
%运算符就是用来格式化字符串的。在字符串内部,%s表示用字符串替换,%d表示用整数替换,有几个%?占位符,后面就跟几个变量或者值,顺序要对应好。如果只有一个%?,括号可以省略。
占位符 替换内容
%d 整数
%f 浮点数
%s 字符串
%x 十六进制整数
其中,格式化整数和浮点数还可以指定是否补0和整数与小数的位数:
print('%2d-%02d' % (3, 1)) 结果为 3-01
print('%.2f' % 3.1415926) 结果为 3.14
如果你不太确定应该用什么,%s永远起作用,它会把任何数据类型转换为字符串。

另一种格式化字符串的方法是使用字符串的format()方法,它会用传入的参数依次替换字符串内的占位符{0}、{1}……,不过这种方式写起来比%要麻烦得多:

>>> 'Hello, {0}, 成绩提升了 {1:.1f}%'.format('小明', 17.125)
'Hello, 小明, 成绩提升了 17.1%'


△Python内置的一种数据类型是列表:list。list是一种有序且可变的集合(可以看成是一个数组),可以随时添加和删除其中的元素。list里面的元素的数据类型也可以不同。用len()函数可以获得list元素的个数;用索引来访问list中每一个位置的元素,记得从左边起索引是从0开始的,从右边起索引是从-1开始的。

>>> classmates = ['Michael', 'Bob', 'Tracy']
>>> classmates
['Michael', 'Bob', 'Tracy']

1追加元素到末尾:>>> classmates.append('Adam')
>>> classmates
['Michael', 'Bob', 'Tracy', 'Adam']
2把元素插入到指定的位置:>>> classmates.insert(1, 'Jack')
>>> classmates
['Michael', 'Jack', 'Bob', 'Tracy', 'Adam']
3要删除list末尾的元素,用pop()方法:>>> classmates.pop()
'Adam'
>>> classmates
['Michael', 'Jack', 'Bob', 'Tracy']
4要删除指定位置的元素,用pop(i)方法,其中i是索引位置:>>> classmates.pop(1)
'Jack'
>>> classmates
['Michael', 'Bob', 'Tracy']
5要把某个元素替换成别的元素,可以直接赋值给对应的索引位置:>>> classmates[1] = 'Sarah'
>>> classmates
['Michael', 'Sarah', 'Tracy']
注意:这几个对list的操作中,只有删除(4和5)是本身有输出的。

另一种有序且指向不变(tuple所谓的“不变”是说,tuple的每个元素,指向永远不变。理解了“指向不变”后,要创建一个内容也不变的tuple怎么做?那就必须保证tuple的每一个元素本身也不能变)的列表叫元组:tuple。tuple和list非常类似,但是tuple一旦初始化就不能修改,当你定义一个tuple时,在定义的时候,tuple的元素就必须被确定下来:>>> classmates = ('Michael', 'Bob', 'Tracy')
只有1个元素的tuple定义时必须加一个逗号,,来消除歧义(>>> t = (1)这种情况下,Python规定,按数学公式中的小括号进行计算,计算结果是1):>>> t = (1,)
>>> t
(1,)
小结:list和tuple是Python内置的有序集合,一个可变,一个不可变。


△条件判断 if语句(注意不要少写了冒号:。)
if <条件判断1>:
<执行1>
elif <条件判断2>:
<执行2>
elif <条件判断3>:
<执行3>
else:
<执行4>
if语句执行有个特点,它是从上往下判断,如果在某个判断上是True,把该判断对应的语句执行后,就忽略掉剩下的elif和else。

△循环(for和while条件语句后面也有冒号:的 )
一种是for...in循环,依次把list或tuple中的每个元素迭代出来
names = ['Michael', 'Bob', 'Tracy']
for name in names:
print(name)
执行这段代码,会依次打印names的每一个元素

Python提供一个range()函数,可以生成一个整数序列,再通过list()函数可以转换为list。比如range(5)生成的序列是从0开始小于5的整数:
>>> list(range(5))
[0, 1, 2, 3, 4]

第二种循环是while循环,只要条件满足,就不断循环,条件不满足时退出循环。在循环中,break语句可以提前退出循环;在循环过程中,也可以通过continue语句,跳过当前的这次循环,直接开始下一次循环。

小结:
break语句可以在循环过程中直接退出循环,而continue语句可以提前结束本轮循环,并直接开始下一轮循环。这两个语句通常都必须配合if语句使用。
要特别注意,不要滥用break和continue语句。break和continue会造成代码执行逻辑分叉过多,容易出错。大多数循环并不需要用到break和continue语句,常常可以通过改写循环条件或者修改循环逻辑,去掉break和continue语句。
程序陷入“死循环”,也就是永远循环下去。这时可以用Ctrl+C退出程序。


△Python内置了字典:dict(key-value存储方式)的支持,dict全称dictionary,在其他语言中也称为map,使用键-值(key-value)存储,具有极快的查找速度。>>> d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
把数据放入dict的方法,除了初始化时指定外,还可以通过key放入(多次对一个key放入value,后面的值会把前面的值冲掉):>>> d['Adam'] = 67
>>> d['Adam']
67
要避免key不存在的错误,有两种办法,一是通过in判断key是否存在:>>> 'Thomas' in d
False
二是通过dict提供的get()方法,如果key不存在,可以返回None,或者自己指定的value:
>>> d.get('Thomas') #注意:返回None的时候Python的交互环境不显示结果。
>>> d.get('Thomas', -1)
-1
要删除一个key,用pop(key)方法,对应的value也会从dict中删除:>>> d.pop('Bob')
75
>>> d
{'Michael': 95, 'Tracy': 85}
请务必注意,dict内部存放的顺序和key放入的顺序是没有关系的。dict是用空间来换取时间的一种方法。
dict的key必须是不可变对象。在Python中,字符串、整数等都是不可变的,因此,可以放心地作为key。而list是可变的,就不能作为key。

set和dict类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在set中,没有重复的key。
要创建一个set,需要提供一个list作为输入集合:
>>> s = set([1, 2, 3])
>>> s
{1, 2, 3}
注意,传入的参数[1, 2, 3]是一个list,而显示的{1, 2, 3}只是告诉你这个set内部有1,2,3这3个元素,显示的顺序也不表示set是有序的。

重复元素在set中自动被过滤:
>>> s = set([1, 1, 2, 2, 3, 3])
>>> s
{1, 2, 3}
通过add(key)方法可以添加元素到set中,可以重复添加,但不会有效果。
通过remove(key)方法可以删除元素。
set可以看成数学意义上的无序和无重复元素的集合,因此,两个set可以做数学意义上的交集、并集等操作:
>>> s1 = set([1, 2, 3])
>>> s2 = set([2, 3, 4])
>>> s1 & s2
{2, 3}
>>> s1 | s2
{1, 2, 3, 4}

注意:
set和dict的唯一区别仅在于没有存储对应的value,但是,set的原理和dict一样,所以,同样不可以放入可变对象,因为无法判断两个可变对象是否相等,也就无法保证set内部“不会有重复元素”。
>>> classmates = ['Michael', 'Bob', 'Tracy'] #list 用[]定义
>>> classmates = ('Michael', 'Bob', 'Tracy') #tuple 用()定义
>>> d = {'Michael': 95, 'Bob': 75, 'Tracy': 85} #dic 用{}来定义
>>> s = set([1, 2, 3]) #set 用()来定义


△函数

数据类型转换
>>> int(12.34)
12
函数名其实就是指向一个函数对象的引用,完全可以把函数名赋给一个变量,相当于给这个函数起了一个“别名”:
>>> a = abs # 变量a指向abs函数
>>> a(-1) # 所以也可以通过a调用abs函数
1

△定义函数
在Python中,定义一个函数要使用def语句,依次写出函数名、括号、括号中的参数和冒号:,然后,在缩进块中编写函数体,函数的返回值用return语句返回。
我们以自定义一个求绝对值的my_abs函数为例:
def my_abs(x):
if not isinstance(x, (int, float)):
raise TypeError('bad operand type')
if x >= 0:
return x
else:
return -x
请注意,函数体内部的语句在执行时,一旦执行到return时,函数就执行完毕,并将结果返回。因此,函数内部通过条件判断和循环可以实现非常复杂的逻辑。
如果没有return语句,函数执行完毕后也会返回结果,只是结果为None。return None可以简写为return。

空函数

如果想定义一个什么事也不做的空函数,可以用pass语句:
def nop():
pass
pass语句什么都不做,那有什么用?实际上pass可以用来作为占位符,比如现在还没想好怎么写函数的代码,就可以先放一个pass,让代码能运行起来。

小结
定义函数时,需要确定函数名和参数个数;
如果有必要,可以先对参数的数据类型做检查;
函数体内部可以用return随时返回函数结果;
函数执行完毕也没有return语句时,自动return None。
函数可以同时返回多个值,但其实就是一个tuple。

练习
请定义一个函数quadratic(a, b, c),接收3个参数,返回一元二次方程:
ax2 + bx + c = 0
的两个解。
提示:计算平方根可以调用math.sqrt()函数:
import math

参考代码如下:

 def quadratic(a, b, c):
if not isinstance(a,(int,float)) | isinstance(b,(int,float)) | isinstance(a,(int,float)):
raise TypeError('bad operand type')
if b**2-4*a*c>=0 :
x1=(-b+math.sqrt(b**2-4*a*c))/(2*a)
x2=(-b-math.sqrt(b**2-4*a*c))/(2*a)
return(x1,x2)
else :
return('wrong ')
# 测试:
print('quadratic(2, 3, 1) =', quadratic(2, 3, 1))
print('quadratic(1, 3, -4) =', quadratic(1, 3, -4)) if quadratic(2, 3, 1) != (-0.5, -1.0):
print('测试失败')
elif quadratic(1, 3, -4) != (1.0, -4.0):
print('测试失败')
else:
print('测试成功') 运行结果:
quadratic(2, 3, 1) = (-0.5, -1.0)
quadratic(1, 3, -4) = (1.0, -4.0)
测试成功

△函数的参数

位置参数

power(x, n),用来计算xn,

 def power(x, n):
s = 1
while n > 0:
n = n - 1
s = s * x
return s #测试代码:
>>> power(5, 2)
25
>>> power(5, 3)
125

power(x, n)函数有两个参数:xn,这两个参数都是位置参数,调用函数时,传入的两个值按照位置顺序依次赋给参数xn

默认参数

由于我们经常计算x2,所以,完全可以把第二个参数n的默认值设定为2:

 def power(x, n=2):
s = 1
while n > 0:
n = n - 1
s = s * x
return s #测试代码:
>>> power(5)
25
>>> power(5, 2)
25

这样,当我们调用power(5)时,相当于调用power(5, 2);而对于n > 2的其他情况,就必须明确地传入n,比如power(5, 3)

从上面的例子可以看出,默认参数可以简化函数的调用。设置默认参数时,有几点要注意:
一是必选参数在前,默认参数在后,否则Python的解释器会报错;
二是如何设置默认参数。

当函数有多个参数时,把变化大的参数放前面,变化小的参数放后面。变化小的参数就可以作为默认参数。

使用默认参数有什么好处?最大的好处是能降低调用函数的难度,而一旦需要更复杂的调用时,又可以传递更多的参数来实现。无论是简单调用还是复杂调用,函数只需要定义一个。

也可以不按顺序提供部分默认参数。当不按顺序提供部分默认参数时,需要把参数名写上。

注意: 定义默认参数要牢记一点:默认参数必须指向不变对象!

为什么要设计strNone这样的不变对象呢?因为不变对象一旦创建,对象内部的数据就不能修改,这样就减少了由于修改数据导致的错误。此外,由于对象不变,多任务环境下同时读取对象不需要加锁,同时读一点问题都没有。我们在编写程序时,如果可以设计一个不变对象,那就尽量设计成不变对象。

可变参数

在Python函数中,还可以定义可变参数。顾名思义,可变参数就是传入的参数个数是可变的,可以是1个、2个到任意个,还可以是0个。

我们以数学题为例子,给定一组数字a,b,c……,请计算a2 + b2 + c2 + ……。

 def calc(*numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum #测试
#调用该函数时,可以传入任意个参数,包括0个参数
>>> calc(1, 2)
5
>>> calc()
0 #Python允许你在list或tuple前面加一个*号,把list或tuple的元素变成可变参数传进去; *nums表示把nums这个list的所有元素作为可变参数传进去。这种写法相当有用,而且很常见
>>> nums = [1, 2, 3]
>>> calc(*nums)
14

成员运算符: not in 、in (判断某个单词里是不是有某个字母)

成员运算符用来判断一个元素是否是另一个元素的成员。 比如说我们可以判断 “hello” 中是否有 “h”, 得到的结果也是True 或者 False。

 >>> "h" in "hello"  # 这里的意思是 “h” 在“Hello” 中,判断后结果为True
True
>>> "h" not in "hello" # 这里的意思是 “h” 不在“Hello” 中,判断后结果为False
False

身份运算符: is、is not(讲数据类型时讲解,一般用来判断变量的数据类型)

用来判断身份。

 >>> a = 123456
>>> b = a
>>> b is a #判断 a 和 b 是不是同一个 123456
True
>>> c = 123456
>>> c is a #判断 c 和 a 是不是同一个 123456
False
>>> c is not a #判断 c 和 a 是不是不是同一个 123456
True

这里我们首先将123456赋值给a,后有将a赋值给b, 这样其实是 a和b 的值都是123456, 但是后面c的值也是123456,为什么 第一次a is b 的结果为True ,c和 a 的结果为False 呢?

原因是这样的: 我们知道程序是运行在内存里的,第一次 我们将123456赋值给a的时候,其实是在内存里开辟了一块空间,将123456放在这块空间里,为了找到这里的123456, 会有一个指向这块空间的地址,这个地址叫做内存地址,是123456存储在内存中的地址。a其实指向的就是存储123456的内存空间的地址。执行了b=a,就是让b指向的地址和a一样。之后我们执行了 c = 123456 ,这里就会再开辟一块内存空间,并将指向该空间的内存地址赋值给c ,这样的话 ,a和b 指向的是同一个123456, c 指向的是另外一个123456 。

关键字参数

可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。

 def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw) #调用
>>> extra = {'city': 'Beijing', 'job': 'Engineer'}
>>> person('Jack', 24, **extra)
name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'} #**extra表示把extra这个dict的所有key-value用关键字参数传入到函数的**kw参数,kw将获得一个dict,注意kw获得的dict是extra的一份拷贝,对kw的改动不会影响到函数外的extra。

关键字参数有什么用?它可以扩展函数的功能。比如,在person函数里,我们保证能接收到nameage这两个参数,但是,如果调用者愿意提供更多的参数,我们也能收到。试想你正在做一个用户注册的功能,除了用户名和年龄是必填项外,其他都是可选项,利用关键字参数来定义这个函数就能满足注册的需求。

命名关键字参数

对于关键字参数,函数的调用者可以传入任意不受限制的关键字参数。至于到底传入了哪些,就需要在函数内部通过kw检查。如果要限制关键字参数的名字,就可以用命名关键字参数,例如,仍以person()函数为例,只接收cityjob作为关键字参数。这种方式定义的函数如下:

 #和关键字参数**kw不同,命名关键字参数需要一个特殊分隔符*,*后面的参数被视为命名关键字参数。
def person(name, age, *, city, job):
print(name, age, city, job) #调用方式如下:
>>> person('Jack', 24, city='Beijing', job='Engineer')
Jack 24 Beijing Engineer #如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了:
def person(name, age, *args, city, job):
print(name, age, args, city, job) #命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错:
>>> person('Jack', 24, 'Beijing', 'Engineer')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: person() takes 2 positional arguments but 4 were given
#上面的错误说的是:由于调用时缺少参数名city和job,Python解释器把这4个参数均视为位置参数,但person()函数仅接受2个位置参数。 #命名关键字参数可以有缺省值,从而简化调用:
def person(name, age, *, city='Beijing', job):
print(name, age, city, job) #由于命名关键字参数city具有默认值,调用时,可不传入city参数:
>>> person('Jack', 24, job='Engineer')
Jack 24 Beijing Engineer

使用命名关键字参数时,要特别注意,如果没有可变参数,就必须加一个*作为特殊分隔符。如果缺少*,Python解释器将无法识别位置参数和命名关键字参数:

def person(name, age, city, job):
# 缺少 *,city和job被视为位置参数
pass

  

参数组合(不是很理解!!!)

在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。

对于任意函数,都可以通过类似func(*args, **kw)的形式调用它,无论它的参数是如何定义的。

 #必选参数:应该就是位置参数
#默认参数:
def power(x, n=2): pass #把位置参数的某一个设置为默认固定的
#可变参数:
def calc(*numbers): pass #可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple(或list)。
#命名关键字参数:
def person(name, age, *, city, job):pass #*后面的参数被视为命名关键字参数(即只接收city和job作为关键字参数)。
#关键字参数:
def person(name, age, **kw):pass #关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。

小结

Python的函数具有非常灵活的参数形态,既可以实现简单的调用,又可以传入非常复杂的参数。

默认参数一定要用不可变对象,如果是可变对象,程序运行时会有逻辑错误!

要注意定义可变参数和关键字参数的语法:

*args是可变参数,args接收的是一个tuple;

**kw是关键字参数,kw接收的是一个dict。

以及调用函数时如何传入可变参数和关键字参数的语法:

可变参数既可以直接传入:func(1, 2, 3),又可以先组装list或tuple,再通过*args传入:func(*(1, 2, 3))

关键字参数既可以直接传入:func(a=1, b=2),又可以先组装dict,再通过**kw传入:func(**{'a': 1, 'b': 2})

使用*args**kw是Python的习惯写法,当然也可以用其他参数名,但最好使用习惯用法。

命名的关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值。

定义命名的关键字参数在没有可变参数的情况下不要忘了写分隔符*,否则定义的将是位置参数。

练习

以下函数允许计算两个数的乘积,请稍加改造,变成可接收一个或多个数并计算乘积:

 # -*- coding: utf-8 -*-
def product(*numbers):
if len(numbers)==0:
raise TypeError('bad operand type')
mul=1
for n in numbers:
mul=mul*n
return mul
# 测试
print('product(5) =', product(5))
print('product(5, 6) =', product(5, 6))
print('product(5, 6, 7) =', product(5, 6, 7))
print('product(5, 6, 7, 9) =', product(5, 6, 7, 9))
if product(5) != 5:
print('测试失败!')
elif product(5, 6) != 30:
print('测试失败!')
elif product(5, 6, 7) != 210:
print('测试失败!')
elif product(5, 6, 7, 9) != 1890:
print('测试失败!')
else:
try:#下面两行代码就意味着这个自定义的函数的参数个数不能为0。
product()
print('测试失败!')
except TypeError:
print('测试成功!')

△递归函数

如果一个函数在内部调用自身本身,这个函数就是递归函数。

在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出。解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。

尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。

尾递归调用时,如果做了优化,栈不会增长,因此,无论多少次调用也不会导致栈溢出。

遗憾的是,大多数编程语言没有针对尾递归做优化,Python解释器也没有做优化,所以,即使把上面的fact(n)函数改成尾递归方式,也会导致栈溢出。

练习

汉诺塔的移动可以用递归函数非常简单地实现。

请编写move(n, a, b, c)函数,它接收参数n,表示3个柱子A、B、C中第1个柱子A的盘子数量,然后打印出把所有盘子从A借助B移动到C的方法,例如:

 # -*- coding: utf- -*-
def move(n, a, b, c):
if n == :
print(a, '-->', c) #a上只有1个盘子
else:#a上有n个盘子
move(n-,a,c,b) #把a上的n-1块移动到b
move(,a,b,c) #把a上的最后一块移动到c
move(n-,b,a,c) #把b上的n-1块移动到c # 期待输出:
# A --> C
# A --> B
# C --> B
# A --> C
# B --> A
# B --> C
# A --> C
move(, 'A', 'B', 'C')

高级特性

在Python中,代码不是越多越好,而是越少越好。代码不是越复杂越好,而是越简单越好。请始终牢记,代码越少,开发效率越高。

△切片

取一个list或tuple或字符串的指定索引范围的部分元素,Python提供了切片(Slice)操作符。

L[0:3]表示,从索引0开始取,直到索引3为止,但不包括索引3,如果第一个索引是0,还可以省略;记住倒数第一个元素的索引是-1

△迭代

如果给定一个list或tuple,我们可以通过for循环来遍历这个list或tuple,这种遍历我们称为迭代(Iteration)。

在Python中,迭代是通过for ... in来完成的。只要作用于一个可迭代对象,for循环就可以正常运行。

如何判断一个对象是可迭代对象呢?方法是通过collections模块的Iterable类型判断:

 >>> from collections import Iterable
>>> isinstance('abc', Iterable) # str是否可迭代
True
>>> isinstance([1,2,3], Iterable) # list是否可迭代
True
>>> isinstance(123, Iterable) # 整数是否可迭代
False

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

 >>> for i, value in enumerate(['A', 'B', 'C']):
... print(i, value)
...
0 A
1 B
2 C

△列表生成式

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

写列表生成式时,把要生成的元素x * x放到前面,后面跟for循环,for循环后面还可以加上if判断:

>>> [x * x for x in range(1, 11) if x % 2 == 0]
[4, 16, 36, 64, 100]

  

△生成器

在Python中,这种列表元素可以按照某种算法推算出来,一边循环一边计算的机制,称为生成器:generator。

要创建一个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>

打印出generator的每一个元素,正确的方法是使用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的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator。

generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。但是用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIterationvalue中:

小结

generator是非常强大的工具,在Python中,可以简单地把列表生成式改成generator,也可以通过函数实现复杂逻辑的generator。

要理解generator的工作原理,它是在for循环的过程中不断计算出下一个元素,并在适当的条件结束for循环。对于函数改成的generator来说,遇到return语句或者执行到函数体最后一行语句,就是结束generator的指令,for循环随之结束。

请注意区分普通函数和generator函数,普通函数调用直接返回结果,generator函数的“调用”实际返回一个generator对象。

△迭代器

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

一类是集合数据类型,如listtupledictsetstr等;

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

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

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

而生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了。

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

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

生成器都是Iterator对象,但listdictstr虽然是Iterable,却不是Iterator

listdictstrIterable变成Iterator可以使用iter()函数:

 >>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True

小结

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

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

集合数据类型如listdictstr等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。

Python的for循环本质上就是通过不断调用next()函数实现的。


■函数式编程

△高阶函数:

变量可以指向函数:函数本身也可以赋值给变量,即:变量可以指向函数。例如

>>> f = abs
>>> f
<built-in function abs>
>>> f(-10)
10

函数名也是变量:函数名其实就是指向函数的变量!

对于abs()这个函数,完全可以把函数名abs看成变量,它指向一个可以计算绝对值的函数!如果把abs指向其他对象,会有什么情况发生?

>>> abs = 10
>>> abs(-10)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable 

abs指向10后,就无法通过abs(-10)调用该函数了!因为abs这个变量已经不指向求绝对值函数而是指向一个整数10

当然实际代码绝对不能这么写,这里是为了说明函数名也是变量。要恢复abs函数,请重启Python交互环境。

传入函数

既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。编写高阶函数,就是让函数的参数能够接收别的函数。

小结

把函数作为参数传入,这样的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式。

△△ map/reduce

      map()函数接收两个参数,一个是函数,一个是Iterablemap将传入的函数依次作用到序列的每个元素,并         把结果作为新的Iterator返回。

reduce把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:

reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)

  

△△ filter

filter()也接收一个函数和一个序列。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后         根据返回值是True还是False决定保留还是丢弃该元素。

可见用filter()这个高阶函数,关键在于正确实现一个“筛选”函数。

注意到filter()函数返回的是一个Iterator,也就是一个惰性序列,所以要强迫filter()完成计算结果,需要       用list()函数获得所有结果并返回list。

小结

     filter()的作用是从一个序列中筛出符合条件的元素。由于filter()使用了惰性计算,所以只有在取                     filter()结果的时候,才会真正筛选并每次返回下一个筛出的元素。

△△ sorted

Python内置的sorted()函数就可以对list进行排序:

>>> sorted([36, 5, -12, 9, -21])
[-21, -12, 5, 9, 36]

此外,sorted()函数也是一个高阶函数,它还可以接收一个key函数来实现自定义的排序,例如按绝对值大小         排序:

>>> sorted([36, 5, -12, 9, -21], key=abs)
[5, 9, -12, -21, 36]

  

△返回函数

△ 匿名函数

△装饰器

△偏函数

廖雪峰Python笔记的更多相关文章

  1. 廖雪峰Python电子书总结

    函数 1.注意:函数的默认参数必须指向不可变对象 未修改前: def add_end(L=[]): L.append('END') return L 存在的问题:如果连续调用多次,会出现多个 'END ...

  2. 廖雪峰python教程的第一个疑问

    函数的参数一节中提到: def add_end(L = []); L.append('END') return L 正常调用add_end时(也就是有参数传入时): >>> add_ ...

  3. 廖雪峰Python实战day1

    一.按照廖雪峰的教程,安装开发环境,问题不大. 1.异步框架aiohttp:$pip3 install aiohttp 2.前端模板引擎jinja2:$ pip3 install jinja2 3.安 ...

  4. 廖雪峰Python学习笔记——类和实例

    Class MyList(list): __metaclass__ = ListMetaclass #它表示在创建MyList这个类时,必须通过 ListMetaclass这个元类的LIstMetac ...

  5. 廖雪峰Python学习笔记——序列化

    序列化 定义:程序运行时所有变量都存在内存中,把变量从内存中变成可存储或可传输的过程称为序列化pickling,在其他语言中称为serialization,marshalling,flattening ...

  6. 廖雪峰Python学习笔记——使用元类

    元类(MetaClasses) 元类提供了一个改变Python类行为的有效方式. 元类的定义是“一个类的类”.任何实例是它自己的类都是元类. class demo(object): pass obj ...

  7. Python廖雪峰学习笔记——操作文件和目录

    查看当前目录的绝对路径: >>>os.path.abspath('.') # 在某个目录下创建一个新目录, # 首先把新目录的完整路径表示出来: >>> os.pa ...

  8. Python廖雪峰学习笔记——单元测试

    定义:对一个模块.一个类.一个函数进行进行正确性检验的测试性工作.当我们对函数或者模块等进行修改时,单元测试就显得尤为重要. 单元测试 = 测试用例(用来测试的数据)+测试模块

  9. 廖雪峰Python3笔记

    主要复习过一遍 简介 略 安装 略 *** 第一个Python程序 第一行的注释: # _*_ coding: utf-8 _*_ #!/usr/bin/env python3 print() 可以接 ...

随机推荐

  1. 分享AWS网站

    1.AWS服务运行状况检测网站:   https://status.amazonaws.cn/ 2.AWS架构白皮书:https://aws.amazon.com/cn/architecture/?a ...

  2. Java调用Http/Https接口(6)--RestTemplate调用Http/Https接口

    RestTemplate是Spring提供的用于访问Http接口的客户端,提供同步的API:在将来的Spring版本中可能会过时,将逐渐被WebClient替代.文中所使用到的软件版本:Java 1. ...

  3. vue-quill-edito 字体倾斜加粗无效

    长话短说,出现这种情况的原因80%-90%的概率在你项目里面有一个全局的 一般在reset.css重置文件中 font-weight:normal; font-style:normal; font-s ...

  4. 如何统一管理单个任务下所有API的同步情况?

    1. 一分钟完成单个API配置 单个API的配置包含:API名称.URL地址.请求方式.参数设置.自定义高级设置. 参数允许用户填写:Text.WebService.Timestamp.DependO ...

  5. React-Native中使用到的一些JS特性

    React Native - 调试技巧及调试菜单说明(模拟器调试.真机调试) https://www.hangge.com/blog/cache/detail_1480.html 1,解构赋值——de ...

  6. SQL Text Literals 文本

    Text Literals 文本 Use the text literal notation to specify values whenever string appears in the synt ...

  7. Termux和Ubuntu建立ssh连接

    1 本机环境 Android:Termux v0.77 作为客户端 Linux:Ubuntu 19.10 作为服务器 两者处于同一局域网下 2 ssh安装 2.1 Termux pkg install ...

  8. Linux代理服务器使用

    1. 介绍 代理(即网络代理)是一种特殊的网络服务, 允许一个网络终端(客户端)通过这个服务与另一个终端(服务器)进行非直接连接,从而提供服务. 其中, 提供代理的网络终端称为代理服务器(Proxy ...

  9. Serializable的作用

    前两天接触到VO,DTO,entity这些概念,发现别人的代码中会有 implements serializable这个东西,之前并没有见过这种写法,就去了解了一下原因 import java.io. ...

  10. Laravel —— could not find driver

    Laravel 中的数据库是以 PDO 的方式连接的 数据库连接失败时,先检查问题所在,再对症下药 本文以 pgsql 为例 1.判断 pgsql 是否启动 $ ps -ef | grep pgsql ...