Python

高考前草草地看了下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函数

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值