高考前草草地看了下Python
什么都没看进去
学了点C之后,决定重新开始看一遍廖雪峰的python教程
希望能够保持每天更新
主要参考资料:廖雪峰python3
Python简介
Python是“龟叔”Guido van Rossum在圣诞节期间,为了打发时间而写的一个编程语言
C语言主要用来开发哪些追求运行速度,充分发挥硬件性能的程序。
Python则是用来编写应用程序的高级编程语言。
Python中提供了非常完善的基础代码库,覆盖了网络,文件,GUI,数据库等等,因此用Python开发,许多功能可以直接使用现成的。
Python的定位就是“优雅”,“明确”,“简单”
但是Python也有缺点,作为解释器语言,Python的代码在执行时会一行一行地翻译成机器码,翻译这个过程耗时,所以很慢。
另外Python的代码不能加密。
第一个Python程序
>>>100+200
300
>>>print('hello,world")
hello,world
打印文字可以用单引号或双引号括起来,但是不可混用
使用文本编辑器
最好是用Sublime Text或者Notepad++,但是绝对不能用word和记事本
输入和输出
输出
>>>print('hello,world')
hello,world
>>>print('The quick brown fox','jumps over','the lazy dog'
The quick brown fox jumps over the lazy dog
>>>print(100+200)
300
>>>print('100+200 =',100+200)
100+200= 300
输入
>>>name=input()
Michael
>>>name
'Michael'
即将Michael存到了name这一变量中去
>>>print(name)
Michael
>>>name=input()
>>>print('hello,',name)
>>>name=input('please enter your name:')
>>>print('hello,',name)
输出为
please enter your name:Michael
hello,Michael
Python基础
Python语法比较简单,采用缩进
#print absolute value of an integer:
a = 100
if a>=0:
print(a)
else:
print(-a)
注意到这里的if和else都有冒号,这一点与C语言不同
始终坚持使用4个空格的缩进
Python大小写敏感
数据类型和变量
数据类型
整数
与C语言不同,Python可以处理任意大小的整数
例如 1,100,-8080,0
同样也可以用十六进制,用0x前缀和0-9,a-f表示
例如:0xff00,0xa5b4c3d2
浮点数
浮点数即小数
对于很大或很小的浮点数,必须要用科学计数法
1.23*10^9就是1.23e9
整数和浮点数在计算机内部存储的方式是不同的,整数运算永远是精确的,而浮点数运算则可能会有四舍五入的误差
字符串
字符串是用单引号‘’或双引号“”括起来的任意文本
例如‘abc’,“xyz”
但是‘’和“”本身不是字符串的一部分
‘abc’只有a,b,c三个字符
对于‘,可以用“”括起来
比如“I‘m ok”的字符是I,’,m,空格,o,k 这6个字符
如果既包含’又包含“,可以用转义字符
例如:‘I’m “ok”!’
而\则可以用两个\表示
>>>print('I\'m ok.')
I'm ok
>>>print('I\'m learning\n Python.')
I'm learning
Python
>>>print('\\\n\\')
\
\
另外,还可以用r’‘表示’'内部的字符串默认不转义
例如
>>>print('\\\t\\')
\ \
>>>print(r'\\\t\\')
\\\t\\
如果字符串很多换行,又不想要\n影响阅读,则可以使用’’’…’’'的形式
>>>print('''line1
...line2
...line3''')
line1
line2
line3
布尔值
一个布尔值只有True和False两种值
>>>3>2
True
>>>3>5
False
同样的,布尔值可以用and,or和not运算
>>>True and True
True
>>>True and False
False
>>>False and False
False
>>>5>3 and 3>1
True
>>>True or False
False
>>>5>3 or 1>3
True
>>>not 1>2
True
布尔值一般用在条件判断中,例如
if age >= 18:
print('adult')
else:
print('teenager')
空值
空值是Python中一个特殊的值,用None表示。
None不能理解为0,因为0是有意义的,而None是一个特殊的空值
变量
变量名必须是大小写英文,数字和_的组合,且不能用数字开头
a=1 变量a是一个整数
t_007=‘T007’ 变量t_007是一个字符串
Answer=True 变量Answer是一个布尔值True
a='ABC'
b=a
a='XYZ'
print(b)
#应当输出
'ABC'
常量
所谓常量即不变的量
Python中,通常用全部大写的变量名表示常量
PI = 3.14159265349
现在介绍Python中的除法
>>>10/3
3.3333333333333333333
>>>9/3
3.0
>>>10//3
字符串和编码
字符编码
C语言中我们已经知道有ASCII码,但是无法处理中文,所以中国制定了GB2312编码,与之对应的,日本把日文编进了Shift_JIS,韩国把韩文编到Eur_kr里,所以这样在多语言混合的文本中,显示出来会有乱码
因此,Unicode应运而生,它把所有语言统一到一套编码里
Unicode通常用2个字节表示1个字符(比较偏僻的则需要4个)
ASCII编码是1字节,Unicode通常是2个字节
所以,如果统一成Unicode编码,乱码问题会消失,但是如果文本全是英文的话,Unicode会比ASCII编码多一倍的存储空间。
所以,为了节约,又出现了UTF-8编码,可以吧一个Unicode字符根据不同的数字大小编码成1-6个字节。
Python的字符串
在Python3中,字符串是以Unicode编码的
>>>print('包含中文的str')
包含中文的str
ord()函数可以获取字符的整数表示
chr()函数可以把编码转换为对应的字符
>>>ord('A')
65
>>>ord('中')
20013
>>>ord(66)
'B'
>>>chr(25991)
'文'
如果知道字符的整数编码,还可以用十六进制这样写str
>>>'\u4e2d\u6587'
'中文'
以Unicode表示的str通过encode()方法可以编码为指定的bytes,例如
>>>'ABC'.encode('ascii')
b'ABC'
>>>'中文'.encode('utf-8')
b'\xe4\xb8\xad\xe6\x96\x87'
>>>'中文'.encode('ascii')
报错#超过编码范围
反过来,可以用decode()把bytes变为str
>>>b'abc'.decode('ascii')
'ABC'
>>>b'\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8')
'中文'
可以用len()函数计算str包含多少个字符
>>>len('ABC')
3
>>>len('中文')
2
len()计算的是字符数,如果换成bytes,则计算字节数
>>>len(b'ABC')
3
>>>len(b'\xe4\xb8\xad\xe6\x96\x87')
6
>>>len('中文'.encode('utf-8))
6
格式化
Python中,格式化方法和C语言一致,用%实现
>>>'Hello,%s' %'world'
'Hello,world'
>>>'Hi,%s,you have $%d.' %('Michael',1000000)
'Hi,Michael,you have $1000000.'
%运算符就是用来格式化字符串
如果只需要一个占位符,则括号可以省略
常见的占位符:
%d 整数
%f 浮点数
%s 字符串
%x 十六进制整数
与C一样,格式化整数和浮点数还可以指定是否补0和整数与小数的位数
>>>'%2d-%02d' %(3,1)
' 3-01'
>>>'%.2f' %3.1415926
'3.14'
有时候,当你不太确实应该用什么的时候,%s永远起作用,它会把任何数据类型转换成字符串
>>>'Age:%s.Gender:%s' %(25,True)
'Age:25.Gender:True'
同样的,用两个%来表示%
使用list和tuple
list
list(列表),是一种有序的集合,可随时添加和删除其中的元素
>>>classmates=['Michael','Bob','Tracy']
>>>classmates
['Michael','Bob','Tracy']
list用[ ]来表示,用len()可以获得list元素的个数
>>>len(classmates)
3
与C一样,list的索引是从0开始的
>>>classmates[0]
'Michael'
>>>classmates[1]
'Bob'
>>>classmates[3]
#报错
切记不要越界,最后一个元素的索引为len(classmates)-1
如果是取最后一个元素,还可以用-1作索引
>>>classmates[-1]
'Tracy'
>>>classmates[-2]
'Bob'
list是一个可变的有序表,可以在末尾追加元素
>>>classmates.append('Adam')
>>>classmates
['Michael','Bob','Tracy','Adam']
也可以把元素插入到指定的位置,比如:
>>>classmates.insert(1,'Jack')
>>>classmates
['Michael','Jack','Bob','Tracy','Adam']
要删除list末尾的元素,可以用pop()
>>>classmates.pop()
'Adam'
>>>classmates
['Michael','Jack','Bob','Tracy']
要删除指定位置的元素,可以用pop(i),其中i是索引位置
>>>classmates.pop(1)
'Jack'
>>>classmates
['Michael','Bob','Tracy']
要进行元素的替换,也可以直接赋值给对应的索引位置
>>>classmates[1]='Sarah'
>>>classmates
['Miachel','Sarah','Tracy']
list里面的元素数据类型可以不同,list元素还可以是另一个list
>>>L=['Apple',123,True]
>>>s=['python','java',['asp','php'],'scheme']
>>>len(s)
4
看这个例子
>>>p=['asp','php']
>>>s=['python','java',p,'scheme']
要拿到php可以写p[1]或者s[2][1],跟C中的二维数组差不多,类似的三维四维等等,不过很少用到。
如果一个list中一个元素也没有,就是一个空的list,长度为0
>>>L=[]
>>>len(L)
0
tuple
tuple与list最大的区别是一经初始化就不能修改
它没有append(),insert()
但它可以正常地获取元素。
tuple不可变,则代码更安全
所以,如果能用tuple代替list最好
>>>t=(1,2)
>>>t
(1,2)
定义一个空的tuple,可以写成()
>>>t=()
>>>t
()
但是,如果要定义一个只有1个元素的tuple,如果你这样定义
>>>t=(1)
>>>t
1
这样定义的不是tuple,而是1这个数,因为()既可以表示tuple,也可以表示数字公式中的小括号,因此会有歧义
Python规定,在这种情况下,按小括号进行计算
所以,只有一个元素的tuple的定义必须加一个逗号,以消除歧义
>>>t=(1,)
>>>t
(1,)
来看一个“可变的"tuple
>>>t=('a','b',['A','B'])
>>>t[2][0]='X'
>>>t[2][1]='Y'
>>>t
('a','b',['X','Y'])
很简单,不解释
条件判断
条件判断
与C差不多
age =3
if age >=18:
print('adult')
elif age >=6:
print('teenager')
else:
print('kid')
唯一要注意的就是冒号
if x:
print('True')
只要x是非零数值,非空字符串,非空list等,就判断为True,否则为False.
再议 input
看这个例子
birth = input('birth=')
if birth <2000:
print('00前')
else:
print('00后')
但是,当你输入1982后,结果会报错
因为input()返回的数据类型是str,str不能直接和整数比较,必须要先把str转换为整数
Python中提供了int()函数来将str转换为整数
循环
循环
Python有两种循环,一种是for…in循环
names=['Michael','Bob','Tracy']
for name in names:
print(name)
执行后,则会输出
Michael
Bob
Tracy
计算1-10的整数和
sum=0
for x in [1,2,3,4,5,6,7,8,9,10]:
sum = sum +x
print(sum)
Python还提供了一个range()函数,生成一个整数数列,再通过list()函数转换为list
>>>list(range(5))
[0,1,2,3,4]
注意,是从0开始到小于5的整数
sum = 0
for x in range(101):
sum = sum +x
print(sum)
第二种循环是while循环,例如计算100以内所有奇数之和
sum = 0
n = 99
while n >0:
sum =sum + n
n=n-2
print(sum)
使用dict和set
dict
Python内置了字典:dict的支持,使用键-值存储,具有极快的查找速度
例如,如果根据同学的名字查找对应的成绩,则需要两个list
names=['Michael','Bob','Tracy']
scores=[95,75,85]
给定一个名字,查找对应的索引,再从scores中取出来
list越长,耗时越长
所以,用dict实现,查找速度都不会变慢
>>>d = {'Michael':95,'Bob':75,'Tracy':85}
>>>d['Michael']
95
dict就是第二种实现方式,直接计算出对应的“页码”,也就是这个数字的内存地址,然后直接取出来,所以速度非常快
把数据放入dict的方法,除了初始化时指定外,还可以通过key放入
>>>d['Adam']=67
>>>d['Adam']
67
一个key只能对应一个value,所以后面输入的值会覆盖前面的值
要避免key不存在,有两种方法
第一种
>>>'Thomas' in d
False
第二种是通过dict的get方法,如果key不存在,则返回None,或者指定的value
>>>d.get('Thomas')
>>>d.get('Thomas',-1)
-1
注意,返回None的时候Python的交互式命令行不显示结果
要删除一个key,用pop(key)方法
>>>d.pop('Bob')
75
>>>d
['Michale':95,'Tracy':85]
注意,dict内部存放的顺序和key的放入顺序是没有关系的
dict的特点
1.查找和插入的速度极快,不会随着key的增加而增加
2.需要占用大量的内存,内存浪费多
而list则恰恰相反,因此,dict是用空间来换取时间的一种方法
正确使用dict非常重要,需要注意dict的key是不可变对象
通过key计算位置的算法称为哈希算法(Hash)
Python中,字符串,整数都是不可变的,可以放心地作为Key,而list是可变的,不能作为key
set
set和dict类似,也是一组key的集合,但不存储value.由于key不能重复,所以,在set中,没有重复的key
要创建一个list,需要提供一个list作为输入集合
>>>s=set([1,2,3])
>>>s
{1,2,3}
重复元素在set中自动被过滤
>>>s=set([1,1,2,2,3,3])
>>>s
{1,2,3}
可以通过add(key)的方法添加元素到set中,可以重复添加,但不会有效果:
>>>s.add(4)
>>>s
{1,2,3,4}
>>>s.add(4)
>>>s
{1,2,3,4}
通过remove(key)的方法可以删除元素
>>>s.remove(4)
>>>s
{1,2,3}
可以把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
再议不可变对象
对于可变对象,比如list,进行操作,list内部的内容是会变化的
>>>a=['c','b','a']
>>>a.sort
>>>a
['a','b','c']
而对于不可变对象,比如str,对str进行操作
>>>a='abc'
>>>a.replace('a','A')
'Abc'
>>>a
'abc'
函数
调用函数
>>>abs(100)
100
>>>abs(-20)
20
abs就是一个返回绝对值的函数
传入参数时,必须要保证参数数量和类型都是正确的
>>>max(1,2)
2
>>>max(2,3,1,-5)
3
数据类型转换
Python内置的函数还包括数据类型转换函数
>>>int('123')
123
>>>int(12.34)
12
>>>float('12.34')
12.34
>>>str(1.23)
'1.23'
>>>bool(1)
True
>>>bool('')
False
还可以把函数名赋给一个变量,更加方便地调用
>>>a=abs
>>>a(-1)
1
定义函数
以自定义一个my_abs函数为例:
def my_abs(x):
if x >= 0:
return x
else:
return -x
空函数
定义一个什么也不做的空函数作为占位符
def nop():
pass
pass还可以用在其他语句中
if age >= 18:
pass
参数检查
如果参数个数不对,Python解释器会自动检查出来
但是如果类型不对,则无法帮助我们检查
再看我们自己定义的my_abs这一个函数
内置函数abs会对参数进行检查,而我们自定义的却不能
所以,需要进行完善
如果需要对参数类型检查,只允许整数和浮点数类型的参数。
可以用内置函数isinstance()进行检查
def my_abs(x):
if not isinstance(x,(int,float)):
raise TypeError('bad operand type')
if x>=0:
return x
else:
return -x
这样如果传入了错误的参数类型,函数就会抛出一个错误
返回多个值
比如游戏中从一个点移动到另一个点
import math
def move(x,y,step,angle=0):
nx = x +step*math.cos(angle)
ny = y -step*math.sin(angle)
return nx,ny
>>>x,y=move(100,100,60,math,pi/6)
>>>print(x,y)
151.96152422706632 70.0
但其实Python返回的值仍然是单一值
>>>r=move(100,100,60,math,pi/6)
>>>print(r)
(151.96152422706632,70.0)
返回值是一个tuple!
函数的参数
位置参数
比如一个计算x^2的函数:
def power(x):
return x*x
而如果要计算x3 ,直到xn ,则可以这么写函数:
def power(x,n):
s = 1
while n>0:
n=n-1
s=s*x
return s
默认参数
当你定义了power(x,n)后,则增加了一个参数
由于我们经常计算x2 ,所以可以设置n的默认参数为2
def power(x,n=2):
...
...
...
这样,当我们再直接调用power(5)时,计算的就是平方
而对于n不为2的其他情况,则需要明确的传入n了,比如power(5,3)
设置默认参数时,有几点要注意:
1.必选参数在前,默认参数在后
2.优先把变化大的参数放前面,变化小的参数放后面
看一个例子
def enroll(name,gender,age=6,city='Beijing'):
print('name=',name)
print('gender=',gender)
print('age=',age)
print('city=',city)
这样大多数学生注册时不需要年龄和城市,只需要必须的两个参数
>>>enroll('Sarah','F')
name:Sarah
gender:F
age:6
city:Beijing
当与默认参数不符时则需要提供额外的信息
enroll('Bob','M',7)
enroll('Adam','M',city='Tianjin')
多个参数,调用时既可以按顺序提供默认参数,也可以不按顺序,但是需要把参数名写上
enroll('Bob','M',7)
enroll('Adam','M',city='Tianjin')
但是,如果使用不当,也会出现问题
定义一个函数,传入一个list,添加一个END后返回
def add_end(L=[]):
L.append('END')
return L
正常调用:
>>>add_end([1,2,3])
[1,2,3,'END']
>>>add_end(['X','Y','Z'])
['X','Y','Z','END']
>>>addd_end()
['END']
>>>add_end()
['END','END']
>>>add_end()
['END','END','END']
显然出现了问题
解释:
此函数在定义的时候,默认参数L的值就被计算出来了,即[],因为默认参数L也是一个变量,它指向[],每次调用函数时,L的内容改变了,所以默认参数的内容也发生了改变
定义默认参数要牢记一点:默认参数必须指向不变对象
那么,如何修正这个函数呢
def add_end(L=None):
if L is None:
L = []
L.append('END')
return L
那么,现在无论调用多少次,都不会有问题
设计不变对象的话,就可以减少由于修改数据导致的错误
编写程序时,可以设计成不变的就尽量设计成不变的
可变参数
所谓可变参数就是传入的参数个数是可变的,可以是0到任意值
def calc(numbers):
sum = 0
for n in numbers:
sum = sum + n*n
return sum
这样调用的时候,需要组装出一个list或者tuple
>>>calc([1,2,3])
14
>>>calc((1,3,5,7))
84
但是,当你在参数面前加一个*号,就可以定义可变参数
def calc(*numbers):
sum = 0
for n in numbers:
sum = sum + n*n
return sum
现在,就可以这样调用
>>>calc(1,2)
5
>>>calc()
0
当你已经有了一个list或者tuple,就可以这样:
>>>nums = [1,2,3]
>>>calc(nums[0],nums[1],nums[2])
14
这样比较繁琐,所以你还可以这样
>>>nums = [1,2,3]
>>>calc(*nums)
14
*nums表示把nums这个list的所有元素作为可变参数传进去。
关键字参数
可变参数自动组装为一个tuple
而关键字参数会自动组装成一个dict
def person(name,age,**kw):
print('name:',name,'age:',age,'others:',kw)
>>>person('Michael',30)
name:Michael age:30 other:{}
>>>person('Bob',35,city='Beijing')
name:Bob age:35 other:{'city':'Beijing'}
>>>person('Adam',45,gender='M',job='Engineer')
name:Adam age:45 other:{'gender':M,'job':'Engineer'}
还可以进行更复杂的调用
>>>extra = {'city':'Beijing','job':'Enginner'}
>>>person('Jack',24,city=extra['city'],job=extra['job'])
name:Jack age:24 other:{'city':'Beijing','job':'Engineer'}
##简单点
>>>extra = {'city':'Beijing','job':'Engineer'}
>>>person('Jack',24,**extra)
name:Jack age:24 other:{'city':'Beijing','job':'Engineer'}
extra表示把extra这个dict的所有key-value用关键字参数传入到函数的kw参数
命名关键字参数
对于关键字参数,调用者可以传入任意不受限制的关键字参数
例如,检查是否有city和job参数
def person(name,age,**kw):
if 'city' in kw:
pass
if 'job' in kw:
pass
print('name:',name,'age:',age,'other:',kw)
但是这样调用者仍然可以传入不受限制的关键字参数
>>>person('Jack',24,city='Beijing',addr='Chaoyang',zipcode=123456)
如果要限制关键字参数的名字,可以用命名关键字参数
例如,只接收city和job作为关键字参数,则可以这么定义
def person(name,age,*,city,job):
print(name,age,city,job)
和**kw不同,只需要一个特殊分隔符*,*后面的参数被视为命名关键字参数
调用方式:
>>>person('Jack',24,city='Beijing',job='Engineer')
Jack 24 Beijing Engineer
但是,命名关键字参数必须传入参数名,不然就会报错
同样,命名关键字参数可以有缺省值
def person(name,age,*,city='Beijing',job):
print(name,age,city,job)
>>>person('Jack',24,job='Engineer')
Jack 24 Beijing Engineer
参数组合
来整理一下
Python中定义函数,可以有
1.必选参数
2.默认参数
3.可变参数
4.关键字参数
5.命名关键字参数
参数定义的顺序必须是:必选参数,默认参数,可变参数/命名关键字参数和关键字参数
def f1(a,b,c=0.*args,**kw):
print('a=',a'b=',b,'c=',c,'args=',args,'kw=',kw)
def f2(a,b,c=0,*,d,**kw):
print('a=',a,'b=',b,'c=',c,'d=',d,'kw=',kw)
>>>f1(1,2)
a=1 b=2 c=0 args=() kw={}
>>>f1(1,2,c=3)
a=1 b=2 c=3 args=() kw={}
>>>f1(1,2,3,'a','b')
a=1 b=2 c=3 args=('a','b') kw={}
>>>f1(1,2,3,'a','b',x=99)
a=1 b=2 c=3 args=('a','b') kw={'x':99}
>>>f2(1,2,d=99,ext=None)
a=1 b=2 c=0 d=99 kw={'ext':None}
但是,通过一个tuple和dict,你也可以调用上述函数
>>>args=(1,2,3,4)
>>>kw={'d':99,'x':'#'}
>>>f1(*args,**kw)
a=1 b=2 c=3 args=(4,) kw={'d':99,'x':#}
>>args=(1,2,3)
>>>kw={'d':88,'x':'#'}
>>>f2(*args,**kw)
a=1 b=2 c=3 d=88 kw={'x':'#'}
所以,对于任意函数,都可以通过func(*args,**kw)来调用它
递归函数
如果一个函数在内部调用自身本身,这个函数就是递归函数
例如,求阶乘的函数可以写成
def fact(n):
if n==1:
return 1
return n*fact(n-1)
如果计算fact(5),则过程如下:
fact(5)
5*fact(4)
5*4*fact(3)
5*4*3*fact(2)
5*4*3*2*fact(1)
5*4*3*2*1
递归函数定义简单,逻辑清晰
但是,使用递归函数需要防止栈溢出
在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧,由于栈的大小不是无限的,所以,递归调用的次数越多,会导致栈溢出
解决栈溢出的方法就是通过优化
尾递归是指,在函数返回时,调用自身本身,并且,return语句不能包含表达式
来看求阶乘的尾递归示例
def fact(n):
return fact_iter(n,1)
def fact_iter(num,product):
if num==1:
return product
return fact_iter(num-1,num*product)
这样再来看fact(5)的调用
fact_iter(5,1)
fact_iter(4,5)
fact_iter(3,20)
fact_iter(2,60)
fact_iter(1,120)
120
但是,大多数编程语言没有针对尾递归优化,所以还是有可能导致栈溢出
高级特性
比如要构造一个1,3,5,7,9…99的列表,可以用循环实现
L=[]
n=1
while n<=99:
L.appen(n)
n=n+2
但是,代码越简单越好,1行能实现的就不要写5行
切片
取一个list或tuple的部分元素是非常常见的操作
给一个list
>>>L=['Michael','Sarah','Tracy','Bob','Jack']
取前N个元素,也就是索引为0-(N-1)的元素,可以用循环
>>>r=[]
>>>n=3
>>>for i in range(n):
r.append(L[i])
>>>r
['Michael','Sarah','Tracy']
用切片可以大大简化这一操作
>>>L[0:3]
['Michael','Sarah','Tracy']
L[0:3]表示从索引0开始取,直到索引3为止,但不包括索引3
即取0,1,2
如果第一个索引是0,还可以忽略
>>>L[:3]
还可以
>>>L[1:3]
同样,还可以倒着取
>>>L[-2:]
['Bob','Jack']
#这里理解为L[-2:0]
>>>L[-2,-1]
['Bob']
切片操作十分有用,可以先创建一个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]
#后10个数
>>>L[-10:]
[90,91,92,93,94,95,96,97,98,99]
#前11-20个数
>>>L[10:20]
[10,11,12,13,14,15,16,17,18,19]
#前10个数,每2个取1个
>>>L[:10:2]
[0,2,4,6,8]
#所有数,每5个取一个
>>>L[::5]
[0,5,10,15,20,25.........,90,95]
tuple也是一种list,唯一区别是tuple不可变,所以,tuple也能用切片操作
>>>(0,1,2,3,4,5)[:3]
(0,1,2)
字符串‘xxx’也可以看成是一种list,因此也能用切片操作
>>>'ABCDEFG'[:3]
'ABC'
>>>'ABCDEFG'[::2]
'ACEG'
迭代
如果给定一个list或者tuple,可以通过for循环来遍历这个list或者tuple,这种遍历成为迭代
Python的for循环可以用在很多地方
比如dict
>>>d={'a':1,'b':2,'c':3}
>>>for key in d:
print(key)
a
c
b
因为dict的存储跟list不同,所以,顺序可能不一样
默认情况下,dict迭代的是key,如果要迭代value
for value in d.values()
如果要同时迭代key和value
for k,v in d.items()
还可以迭代字符串
>>>for ch in 'ABC':
print(ch)
A
B
C
所以,只要for循环作用于一个可迭代对象,for循环就可以正常运行
那么,如何判断一个对象是否是可迭代对象呢?
通过collections模块的Iterable判断
>>>from collections import Iterable
>>>isinstance('abc',Iterable) #str是否可迭代
True
>>>isinstance([1,2,3],Iterable) #list是否可迭代
True
>>>isinstance(123,Iterable) #整数是否可迭代
False
那么Python如何实现下标循环呢?
Python内置的enumerate函数可以把一个list变成索引-元素对,
这样就可以在for循环中用时迭代索引和元素本身
>>>for i,value in enumerate(['A','B','C']):
print(i,value)
0 A
1 B
2 C
在上面的for循环中,同时引用了两个变量
>>>for x,y in [(1,1),(2,4),(3,9)]:
print(x,y)
1 1
2 4
3 9
列表生成式
如果要生成[1* 1,2* 2…10*10]怎么做?
可以通过循环
>>>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]
还可以加上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']
还可以利用列表生成式列出当前目录下的所有文件和目录名
>>>import os
>>>[d for d in os.listdir('.')]
.....
列表生成式还可以使用两个变量来生成list
>>>d={'x':'A','y':'B','z':'C'}
>>>[k+'='+v for k,v in d,items()]
['y=B','x=A','z=C']
还可以把一个list中所有的字符串变成小写
>>>L=['Hello','World','IBM','Apple']
>>>[s.lower() for s in L]
['hello','world','ibm','apple']
生成器
通过列表生成式,可以直接创建一个列表,但是,收到内存限制,列表容量肯定是有限的。
所以,我们可以通过某种算法,在循环的过程中不断推算出后续的元素,这样就不必创建完整的list,从而节省大量的空间。
在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>
创建L和g的区别仅在于最外层的[]和(),L是一个list,而g是一个generator。
打印generator的每一个元素可以这样
>>>next(g)
0
>>>next(g)
1
>>>next(g)
4
.
.
.
但是这样太麻烦了,所以还可以使用for循环
>>>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非常强大,如果推算的算法比较复杂,可以用函数来实现
斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:
def fib(max):
n,a,b=0,0,1
while n < max:
print(b)
a,b=b,a+b
n=n+1
return 'done'
这样直接调用就可以输出前N个数
>>>fib(6)
1
1
2
3
5
8
'done'
可以看出,fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似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'
如果一个函数中包括yield关键字,那么这个函数就不是一个普通函数,而是一个generator
>>>f=fib(6)
>>>f
<generator object fib at 0x104feaaa0>
generator和函数的执行流程不一样,函数是顺序执行,遇到return语句或者最后一行函数语句就返回。
而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续进行
举一个例子,定义一个generator,依次返回数字1,3,5
def odd():
print('step1')
yield 1
print('step2')
yield (3)
print('step3')
yield(5)
调用该generator时,首先要生成一个generator对象,然后用next()函数不断获得下一个值
>>>o = odd()
>>>next(0)
step 1
1
>>>next(o)
step 2
3
>>>next(o)
step 3
5
>>>next(o)
Traceback (most...
...
可以看到,odd不是普通函数,而是generator,在执行过程中,遇到yield就中断,下次又继续执行。执行3次yield后,已经没有yield可以执行了,于是报错
同样的,把函数改成generator后,我们基本上从来不会用next()来获取下一个返回值,而是直接用for循环来迭代
>>>for n in fib(6):
print(n)
...
...
1
1
2
3
5
8
但是用for循环调用generator的时,会拿不到返回值,如果要拿到,必须捕获StopIteration错误,返回值包含在StopIteration的value中:
>>>g=fib(6)
>>>while True:
try:
x=next(g)
print('g:',x)
except StopIteration as e:
pirnt('Generator return value:',e.value)
break
g:1
g:1
g:2
g:3
g:5
g:8
Generator return value:done
迭代器
可以直接作用于for循环的数据类型有以下几种:
一类是集合数据类型,如list,tuple,dict,set,str等
一类是generator,包括生成器和带yield的generation function
这些可以直接作用于for循环的对象统称为可迭代对象:Iterable
可以使用isinstance()判断一个对象是否是Iterable对象
>>>from collections import Iterable
>>>isinstance([],Iterable)
True
>>>isinstance({},Iterable)
True
>>>isinstance('abc',Iterable)
True
>>>isinstance((x for x in range(10)),Iterable)
True
>>>isinstance(100,Iterable)
False
而生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续下一个值了。
可以被next()函数调用并不断返回下一个值得对象成为迭代器:Iterable
可以使用isinstance()判断一个对象是否是Iterable对象:
>>>from collections import Iterator
>>>isinstance((x for x in range(10)),Iterator)
True
>>>isinstance([],Iterator)
False
>>>isinstance({},Iterator)
False
>>>isinstance('abc',Iterator)
False
生成器都是Iterator对象,但list,dict,str虽然是Iterable,却不是Iterator
把list,dict,str等Iterable变成Iterator可以使用iter()函数:
>>>isinstance(iter([]),Iterator)
True
>>>isinstance(iter('abc'),Iterator)
True
为什么list,dict,str等数据类型不是Iterator?
这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个错误流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算
Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。
函数式编程
函数是Python内建支持的一种封装,通过把大段的代码拆分为函数,通过调用来将复杂任务分解成简单任务
而函数式编程,虽然是面向过程的程序设计,但思想更加接近于数学计算
在计算机层次上,CPU执行的是加减乘除的指令代码,以及判断和跳转指令,所以,汇编语言是最贴近计算机的语言
而计算则是指数学意义上的计算,越是抽象的计算,离计算机硬件越远。
对应到编程语言,越是低级的语言,越贴近于计算机,抽象程度越低,执行效率越高,比如C语言。
函数式编程的一个特点就是,允许函数本身作为参数传入另一个函数,还允许返回一个函数。
函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!
Python允许使用变量,因此,Python不是纯函数式编程语言。
高阶函数
变量可以指向函数
>>>abs(-10)
10
可以把函数本身赋值给变量
>>>f=abs
>>>f
<built-in function abs>
>>>f(-10)
10
函数名也是变量
对于abs()这个函数,完全可以把函数名abs看成变量,它指向一个可以计算绝对值的函数
传入函数
一个函数也可以接收另一个函数作为参数,这种函数就称之为高阶函数
>>>def add(x,y,f):
return f(x)+f(y)
>>>add(-5,6,abs)
11
map/reduce
map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数一次作用到序列的每一个元素,并把结果作为新的Iterable返回
例如
>>>def f(x):
return x*x
>>>r=map(f,[1,2,3,4,5,6,7,8,9])
>>>list(r)
[1,4,9,16,25,36,49,64,81]
固然可以用一个循环来实现这个功能,但map()作为高阶函数,把运算规则抽象了
>>>list(map(fstr,[1,2,3,4,5,6,7,8,9]))
['1','2','3','4','5','6','7','8','9']
再来看reduce,reduce就是把一个函数作用在一个序列[x1,x2,x3…]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算
reduce(f,[x1,x2,x3,x4])=f(f(f(x1,x2),x3),x4)
比如对一个序列求和
>>>from functools import reduce
>>>def add(x,y):
return x+y
>>>reduce(add,[1,3,5,7,9])
25
如果要把[1,3,5,7,9]变成整数13579,reduce就可以派上用场
>>>from functools import reduce
>>>def fn(x,y):
return x*10+y
>>>reduce(fn,[1,3,5,7,9])
13579
filter
Python内建的fliter()函数用于过滤列表
和map()类似,filter()也接收一个函数和一个序列,和map()不同的是,fliter()把传入的函数一次作用于每个元素,然后根据返回值是True还是False来决定保留或丢弃该元素
例如,在一个list中,删掉偶数,只保留奇数,可以这么写:
def is_odd(n):
return n%2==1
list(filter(is_odd,[1,2,4,5,6,9,10,15])
[1,5,9,15]
把一个序列中的空字符串删掉,可以这么写:
def not_empty(s):
return s and s.strip()
list(filter(not_empty,['A','','B',None,'C',' ']))
['A','B','C']
用filter求素数
可以通过埃氏筛法来求素数
def _odd_iter(): #构造一个从3开始的奇数序列
n=1
while Ture:
n=n+2
yield n
def _not_divisible(n):
return lambda x : x%n>0
def primes():
yield 2
it = _odd_iter()
while True:
n=next(it)
yield n
it = filter (_not_divisible(n),it)
for n in primes():
if n <1000:
print (n)
else:
break
sorted
排序算法
在程序中,经常要用到排序算法
通常规定,对于两个元素x和y,如果认为x<y,则返回-1,如果认为x==y,则返回0,如果认为x>y,则返回1
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]
key指定的函数将作用于list的每一个元素上,并根据key函数返回的结果进行排序。
再来看一个字符串排序的例子
>>>sorted(['bob,'about','Zoo','Credit'])
['Credit','Zoo','about','bob']
默认情况下是按照ASCII的大小进行比较的
如果忽略大小写,按照字母序进行排序
>>>sorted(['bob','about','Zoo','Credit'],key=str.lower)
['about','bob','Credit','Zoo']
如果要进行反向排序,不必改动key函数,可以传入第三个参数reverse=True:
>>>sorted(['bob','about','Zoo','Credit'],key=str.lower,reverse=True)
['Zoo','Credit','bob','about']
返回函数
函数作为返回值
高阶函数还可以把函数作为结果值返回
例如实现一个可变参数的求和
def lazy_sum(*args):
def sum():
ax=0
for n in args:
ax=ax+n
return ax
return sum
这样,当我们调用lazy_sum()时,返回的并不是求和结果,而是求和函数:
>>>f=lazy_sum(1,3,5,7,9)
>>>f
<function lazy_sum.<locals>.sum at 0x101c6ed90>
只有真真调用f时,才真正计算求和的结果
>>>f()
25
当我们调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数
>>>f1=lazy_sum(1,3,5,7,9)
>>>f2=lazy_sum(1,3,5,7,9)
>>>f1==f2
False
闭包
来看一个例子
def count():
fs=[]
for i in range(1,4):
def f():
return i*i
fs.append(f)
return fs
f1,f2,f3=count()
>>>f1()
9
>>>f2()
9
>>>f3()
9
都等于9!
原因就在于返回的函数引用了变量i,但它并非立刻执行。
而是等到3个函数都返回时,它们所引用的变量i已经变成了3,因此最终的结果为9
返回闭包时,需要牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量
如果一定要引用循环变量怎么办?
方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变
def count():
def f(j):
def g():
return j*j
return g
fs=[]
for i in range (1,4):
fs.append(f(i))
return fs
再来看结果
>>>f1,f2,f3=count()
>>>f1()
1
>>>f2()
4
>>>f3()
9
缺点就是代码较长
匿名函数
当我们传入函数时,有些时候,不需要显式地定义函数,直接传入匿名函数更加的方便
在Python中,对匿名函数提供了有限支持
例如
>>>list(map(lambda x:x*x [1,2,3,4,5,6,7,8,9]))
[1,4,9,16,25,36,29,64,81]
通过对比可以看出,匿名函数lambda x:x*x实际上就是
def f(x):
return x*x
关键字lambda表示匿名函数,冒号前面的x表示函数参数
匿名函数有一个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果
用匿名函数的一个好处就是不用担心函数名冲突。
同样也可以把匿名函数赋值给一个变量,再利用变量来调用该函数
>>>f=lambda x:x*x
>>>f(5)
25
装饰器
通过变量也能够调用函数
>>>def now():
print('2015-3-25')
>>>F=now
>>>f()
2015-3-25
函数对象有一个_name_属性,可以拿到函数的名字
>>>now._name_
'now'
>>>f._name_
'now'
如果我们要增强now()函数的功能,比如在函数调用前后打印日志,但又不希望修改now()函数的定义,这种在代码运行期间动态增加功能的方式,称之为装饰器(Decorator)
本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下:
def log(func):
def warpper(*args,**kw):
print('call %s():'%func._name_)
return func(*args,**kw)
return wrapper
观察上面的log,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。
我们可以借助Python的@语法,把decorator置于函数的定义处:
@log
def now():
print('2015-3-25')
>>>now()
call now():
2015-3-25
把@log放在now()函数的定义处,相当于执行了语句:
now=log(now)
由于log()是一个decorator,返回一个函数。所以,原来的now()函数仍然存在,只是现在同名的now变量指向了新的函数,于是调用now()将执行新函数,即在log()函数中返回的wrapper()函数。
wrapper()函数的参数定义是(*args,j**kw),因此,wrapper()函数可以接受任意参数的调用。在wrapper()函数内,首先打印日志,再紧接着调用原始函数。
如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写起来会更复杂。
比如,要自定义log的文本。
def log(text):
def decorator(func):
def wrapper(*args,**kw):
print('%s %s():'%(text,func._name_))
return func(*args,**kw)
return wrappper
return decorator
这个三层嵌套的decorator用法如下:
@log('execute')
def now():
print('2015-3-25')
执行结果如下:
>>>now()
execute now():
2015-3-25
和两层嵌套的decorator相比,3层嵌套的效果是这样的:
>>>now=log('execute')(now)
首先执行log(‘execute’),返回的是decorator函数,再调用返回的函数,参数now函数,返回值最终是wrapper函数