python简介
python的优点:
1、Python为我们提供了非常完善的基础代码库,覆盖了网络、文件、GUI、数据库、文本等大量内容。
2、用Python开发,许多功能不必从零编写,直接使用现成的即可。
3、Python有大量的第三方库。
python的缺点:
缺点就是运行速度慢,和C程序相比非常慢,因为Python是解释型语言,你的代码在执行时会一行一行地翻译成CPU能理解的机器码,这个翻译过程非常耗时,所以很慢。
python适合开发的应用:
1、网络应用,包括网站、后台服务等等;
2、许多日常需要的小工具,包括系统管理员需要的脚本任务等等;
3、把其他语言开发的程序再包装起来,方便使用。
Python基础
#print absolute value of an integer:
a=100
if a >= 0:
print(a)
else:
print(-a)
以#开头的语句是注释。
当语句以冒号 : 结尾时,缩进的语句视为代码块。
按照约定俗成的管理,应该始终坚持使用4个空格的缩进。
Python是大小写敏感的。
数据类型和变量
数据类型
在Python中,能够直接处理的数据类型有以下几种:
1.整数:
十六进制:0xff00
,0xa5b4c3d2
2.浮点数:
(1)数学写法:1.23
,-1.23
(2)科学计数法:1.23x109 : 1.23e9
或12.3e8
0.000012 : 1.2e-5
3.字符串:
字符串是以单引号'
或双引号"
括起来的任意文本,例:'abc'
,''xyz
既包含'
又包含"
:'I\'m \"OK\"!'
在' "前加
\n
表示换行
如果字符串内部有很多换行,用\n写在一行里不好阅读,为了简化,Python允许用 '''...'''
的格式表示多行内容。
4.布尔值:True
、False
布尔值可以用and
、or
和not
运算。
5.空值:None
None
不能理解为0
,因为0
是有意义的,而None
是一个特殊的空值。
6.变量:
变量名必须是大小写英文、数字和_
的组合,且不能用数字开头。
7.常量:
通常用全部大写的变量名表示常量:
PI = 3.14159265359
在Python中,有两种除法,一种除法是/
:
>>> 10 / 3
3.3333333333333335
/
除法计算结果是浮点数,即使是两个整数恰好整除,结果也是浮点数:
>>> 9 / 3
3.0
还有一种除法是//
,称为地板除,两个整数的除法仍然是整数:
>>> 10 // 3
3
字符编码
Python的字符串
对于单个字符的编码,Python提供了ord()
函数获取字符的整数表示,chr()
函数把编码转换为对应的字符:
>>> ord('A')
65
>>> ord('中')
20013
>>> chr(66)
'B'
>>> chr(25991)
'文'
如果知道字符的整数编码,还可以用十六进制这么写str
:
>>> '\u4e2d\u6587'
'中文'
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'
>>> '中文'.encode('ascii')
Traceback (most recent call last):
File "", line 1, in
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1: ordinal not in range(128)
格式化:
我们经常会输出类似'亲爱的xxx你好!你xx月的话费是xx,余额是xx'
之类的字符串。
在Python中,采用的格式化方式和C语言是一致的,用%实现,例:
>>> 'Hello, %s' % 'world'
'Hello, world'
>>> 'Hi, %s, you have $%d.' % ('Michael', 1000000)
'Hi, Michael, you have $1000000.'
%
运算符就是用来格式化字符串的。在字符串内部,有几个%?
占位符,后面就跟几个变量或者值,顺序要对应好。
如果只有一个%?
,括号可以省略。
占位符 | 替换内容 |
---|---|
-%d | 整数 |
-%f | 浮点数 |
-%s | 字符串 |
-%x | 十六进制整数 |
format()
另一种格式化字符串的方法是使用字符串的format()
方法,它会用传入的参数依次替换字符串内的占位符{0}
、{1}
……,不过这种方式写起来比%
要麻烦得多:
>>> 'Hello, {0}, 成绩提升了 {1:.1f}%'.format('小明', 17.125)
'Hello, 小明, 成绩提升了 17.1%'
使用list和tuple
list
操作小结:
list[i]
访问(正向0,反向-1) 后=
可修改值
len(list)
获取list长度
.append()
追加元素至末尾
.insert(1,'Jack)
插入元素至指定位置
.pop()
删除末尾元素
.pop(i)
删除指定位置元素
Python内置的一种数据类型是列表:list。
list是一种有序的集合,可添加和删除其中的元素。
>>> classmates = ['Michael', 'Bob', 'Tracy']
>>> classmates
['Michael', 'Bob', 'Tracy']
变量classmates
就是一个list。len()
函数获得list元素个数:
>>> len(classmates)
3
用索引访问list中的元素,索引从0
开始:
>>> classmates[0]
'Michael'
>>> classmates[1]
'Bob'
>>> classmates[2]
'Tracy'
>>> classmates[3]
Traceback (most recent call last):
File "", line 1, in
IndexError: list index out of range
当索引超出了范围时,Python会报一个IndexError
错误。
可以用-1
做索引,直接获取最后一个元素:
>>> classmates[-1]
'Tracy'
以此类推,可以获取倒数第2个、倒数第3个。
list是一个可变的有序表,所以,可以往list中追加元素到末尾:
>>> classmates.append('Adam')
>>> classmates
['Michael', 'Bob', 'Tracy', 'Adam']
把元素插入到指定的位置,比如索引号为1
的位置:
>>> 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()
'Adam'
>>> classmates
['Michael', 'Jack', 'Bob', 'Tracy']
把某个元素替换成别的元素,可以直接赋值给对应的索引位置:
>>> classmates[1] = 'Sarah'
>>> classmates
['Michael', 'Sarah', 'Tracy']
list里面的元素的数据类型也可以不同,比如:
>>> L = ['Apple', 123, True]
ist元素也可以是另一个list,比如:
>>> s = ['python', 'java', ['asp', 'php'], 'scheme']
>>> len(s)
4
要注意s
只有4个元素,其中s[2]
又是一个list,拆开写容易理解:
>>> p = ['asp', 'php']
>>> s = ['python', 'java', p, 'scheme']
要拿到'php'
可以写p[1]
或者s[2][1]
,因此s
可以看成是一个二维数组,类似的还有三维、四维……数组,不过很少用到。
tuple
另一种有序列表叫元组:tuple。
tuple和list非常类似,但是tuple一旦初始化就不能修改。
比如同样是列出同学的名字:
>>> classmates = ('Michael', 'Bob', 'Tracy')
你可使用classmates[0]
,classmates[-1]
,不能赋值。
因为tuple不可变,所以代码更安全。
tuple的陷阱:
定义一个tuple,在定义时,tuple的元素必须被确定,例:
>>> t = (1, 2)
>>> t
(1, 2)
如果要定义一个空的tuple,可以写成()
:
>>> t = ()
>>> t
()
但是,要定义一个只有1个元素的tuple,如果你这么定义:
>>> t = (1)
>>> t
1
定义的不是tuple,是1
这个数!这是因为括号()
既可以表示tuple,又可以表示数学公式中的小括号,这就产生了歧义,
Python规定,这种情况按小括号进行计算,计算结果是1
。
所以,只有1个元素的tuple定义时必须加一个逗号,
,来消除歧义:
>>> t = (1,)
>>> t
(1,)
最后看一个“可变的”tuple:
>>> t = ('a', 'b', ['A', 'B'])
>>> t[2][0] = 'X'
>>> t[2][1] = 'Y'
>>> t
('a', 'b', ['X', 'Y'])
这个tuple定义的时候有3个元素,分别是·'a'·,·'b'·和一个list。
当我们把list的元素
'A'
和'B'
修改为'X'
和'Y'
后,tuple变为:
归纳:tuple的不变,指的是tuple中指向的元素没有变,比如指向
'a'
,'b'
,同理,tuple指向的list,这个指向也没有变,变化的list中的内容。
条件判断
if语句
例:
if <条件判断1>:
<执行1>
elif <条件判断2>:
<执行2>
elif <条件判断3>:
<执行3>
else:
<执行4>
if判断条件还可以简写,比如写:
if x:
print('True')
只要x是非零数值、非空字符串、非空list等,就判断为True
,否则为False
。
input
例:
birth = input('birth: ')
if birth < 2000:
print('00前')
else:
print('00后')
输入1982
,结果报错:
Traceback (most recent call last):
File "", line 1, in
TypeError: unorderable types: str() > int()
因为input()
返回的数据类型是str
,str
不能直接和整数比较,必须先把str
转换成整数。
Python提供了int()
函数来完成这件事情:
s = input('birth: ')
birth = int(s)
if birth < 2000:
print('00前')
else:
print('00后')
int()
函数发现一个字符串不是合法的数字时就会报错,程序退出。
循环
for...in
循环,依次把list或tuple中的每个元素迭代出来,例:
names = ['Michael', 'Bob', 'Tracy']
for name in names:
print(name)
依次打印names
的每一个元素:
Michael
Bob
Tracy
for x in ...
循环就是把每个元素代入变量x
,然后执行缩进块的语句。
计算1-100的整数之和:
range()
函数生成一个整数序列,再通过list()
函数可以转换为list。
range(101)
生成0-100的整数序列:
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)
break
提前退出循环
n = 1
while n <= 100:
if n > 10: # 当n = 11时,条件满足,执行break语句
break # break语句会结束当前循环
print(n)
n = n + 1
print('END')
continue
continue
的作用是提前结束本轮循环,并直接开始下一轮循环。
n = 0
while n < 10:
n = n + 1
if n % 2 == 0: # 如果n是偶数,执行continue语句
continue # continue语句会直接继续下一轮循环,后续的print()语句不会执行
print(n)
dict和set
总结:
dict:
1.初始化:d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
2.通过key放入:d['Adam'] = 67
3.一个key对应一个value
4.判断key是否存在:
'Thomas' in d
False
5.返回指定key的值/返回指定位置的key:
>>> d.get('Thomas')
>>> d.get('Thomas', -1)
-1
6.删除一个key:pop(key)
对应的value也会被删除
set:
1.初始化:
s = set([1, 1, 2, 2, 3, 3])
>>> s
{1, 2, 3}
2.添加元素到set中:.add(key)
3.删除元素:.remove(key)
dict
Python内置了字典:dict的支持,dict全称dictionary,在其他语言中也称为map,使用键-值(key-value)存储,具有极快的查找速度。
例:根据同学的名字查找对应的成绩:
用dict实现------“名字”-“成绩”对照表,直接根据名字查找成绩,无论这个表有多大,查找速度都不会变慢。
用Python写一个dict如下:
>>> d = {'Michael': 95, 'Bob': 75, 'Tracy': 85}
>>> d['Michael']
95
给定一个名字,'Michael',dict在内部就可以直接计算出Michael对应的存放成绩的“页码”,也就是95这个数字存放的内存地址,直接取出来。
key-value存储方式::前是key,:后是value
把数据放入dict,还可以通过key放入:
>>> d['Adam'] = 67
>>> d['Adam']
67
一个key只对应一个value,多次对一个key放入value,后值会把前值冲掉:
>>> d['Jack'] = 90
>>> d['Jack']
90
>>> d['Jack'] = 88
>>> d['Jack']
88
如果key不存在,dict就会报错:
>>> d['Thomas']
Traceback (most recent call last):
File "", line 1, in
KeyError: 'Thomas'
避免key不存在的错误:
1.通过in判断key
是否存在:
>>> 'Thomas' in d
False
2.通过dict提供的get()
方法,如果key
不存在,可以返回None
,或者自己指定的value
:
>>> d.get('Thomas')
>>> d.get('Thomas', -1)
-1
删除一个key,用pop(key)
,对应的value也会从dict中删除:
>>> d.pop('Bob')
75
>>> d
{'Michael': 95, 'Tracy': 85}
dict内部存放的顺序和key放入的顺序是没有关系的。
和list比较,dict有以下几个特点:
1.查找和插入的速度极快,不会随着key的增加而变慢;
2.需要占用大量的内存,内存浪费多。
而list相反:
1.查找和插入的时间随着元素的增加而增加;
2.占用空间小,浪费内存很少。
所以,dict是用空间来换取时间的一种方法。
dict的key必须是不可变对象。
因为dict根据key来计算value的存储位置,若每次计算相同的key得出的结果不同,dict内部就完全混乱了。这个通过key计算位置的算法称为哈希算法(Hash)。
要保证hash的正确性,作为key的对象就不能变。在Python中,字符串、整数等都是不可变的,因此,可以放心地作为key。而list是可变的,就不能作为key:
>>> key = [1, 2, 3]
>>> d[key] = 'a list'
Traceback (most recent call last):
File "", line 1, in
TypeError: unhashable type: 'list'
set
也是一组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中,可以重复添加,但不会有效果:
>>> 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}
再议不可变对象
对于可变对象,比如list,对list进行操作,list内部的内容是会变化的,比如:
>>> a = ['c', 'b', 'a']
>>> a.sort()
>>> a
['a', 'b', 'c']
而对于不可变对象,比如str,对str进行操作呢:
>>> a = 'abc'
>>> a.replace('a', 'A')
'Abc'
>>> a
'abc'
虽然字符串有个replace()方法,也确实变出了'Abc',但变量a最后仍是'abc',应该怎么理解呢?
我们先把代码改成下面这样:
>>> a = 'abc'
>>> b = a.replace('a', 'A')
>>> b
'Abc'
>>> a
'abc'
a是变量,'abc'
才是字符串对象!对象a的内容是'abc'
,指a本身是变量,指向对象的内容是'abc'
。
当调用 a.replace('a', 'A')
,实际上replace
是作用在字符串对象'abc'
上,replace
没有改变字符串'abc'
的内容。
相反,replace
创建一个新字符串'Abc'
并返回,如果我们用变量b指向该新字符串,就容易理解了,变量a仍指向原有的字符串'abc'
,变量b指向新字符串'Abc'
。
函数
调用函数
求绝对值的函数:abs( )
求max函数:max()
可传入任意多参数
数据类型转换
>>> int('123')
123
>>> int(12.34)
12
>>> float('12.34')
12.34
>>> str(1.23)
'1.23'
>>> str(100)
'100'
>>> bool(1)
True
>>> bool('')
False
可以把函数名赋给一个变量,相当于给函数起一个“别名”:
>>> a = abs # 变量a指向abs函数
>>> a(-1) # 所以也可以通过a调用abs函数
1
定义函数
定义一个函数要使用def
语句,依次写出函数名、括号、括号中的参数和冒号:
,在缩进块中编写函数体,函数返回值用return语句返回。
例:
def my_abs(x):
if x >= 0:
return x
else:
return -x
空函数
定义一个什么也不做的空函数,可以用pass
语句:
def nop():
pass
返回多个值
从一个点移动到另一个点,给出坐标、位移和角度,就可以计算出新的坐标:
import math
def move(x, y, step, angle=0):
nx = x + step * math.cos(angle)
ny = y - step * math.sin(angle)
return nx, ny
import math
语句表示导入math
包,并允许后续代码引用math
包里的sin
、cos
等函数。
>>> 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
函数的参数
位置参数
def power(x):
return x * x
对于power(x)函数,参数x就是一个位置参数。
默认参数
经常计算x2,所以,可以把第二个参数n的默认值设定为2:
def power(x, n=2):
s = 1
while n > 0:
n = n - 1
s = s * x
return s
调用power(5)
= 调用power(5, 2)
。
必选参数在前,默认参数在后。
默认参数必须指向不变对象。
一个大坑:
先定义一个函数,传入一个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']
当你使用默认参数调用时,一开始结果也是对的:
>>> add_end()
['END']
但是,再次调用add_end()时,结果就不对了:
>>> add_end()
['END', 'END']
>>> add_end()
['END', 'END', 'END']
原因:Python函数在定义的时候,默认参数L的值就被计算出来了,即[],因为默认参数L也是一个变量,它指向对象[ ],每次调用该函数,如果改变了L的内容,下次调用时,默认参数的内容就变了,不再是函数定义时的[ ]了。
可以用None这个不变对象来实现:
def add_end(L=None):
if L is None:
L = []
L.append('END')
return L
创建不变对象,如None
,str
,对象内部的数据就不能修改,此外由于对象不变,多任务环境下同时读取对象不需要加锁,同时读一点问题都没有。
可变参数
例:给定一组数字a,b,c……,请计算a2 + b2 + c2 + ……
由于参数个数不确定,我们首先想到可以把a,b,c……作为一个list或tuple传进来,函数定义如下:
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
如果利用可变参数,调用函数的方式可以简化成这样:
>>> 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
如果已经有一个list或者tuple,要调用一个可变参数怎么办:
>>> nums = [1, 2, 3]
>>> calc(nums[0], nums[1], nums[2])
14
太繁琐,所以Python允许你在list或tuple前面加一个*号,把list或tuple的元素变成可变参数传进去:
>>> nums = [1, 2, 3]
>>> calc(*nums)
14
关键字参数
可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。请看示例:
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)
函数person除了必选参数name和age外,还接受关键字参数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'}
它可以扩展函数的功能。比如,在person
函数里,我们保证能接收到name
和age
这两个参数,但是,如果调用者愿意提供更多的参数,我们也能收到。
和可变参数类似,也可以先组装出一个dict,然后,把该dict转换为关键字参数传进去:
>>> extra = {'city': 'Beijing', 'job': 'Engineer'}
>>> 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
参数,kw
将获得一个dict,注意kw
获得的dict是extra
的一份拷贝,对kw
的改动不会影响到函数外的extra
。
命名关键字参数
对于关键字参数,函数的调用者可以传入任意不受限制的关键字参数。至于到底传入了哪些,就需要在函数内部通过kw检查。
以person()
函数为例,我们希望检查是否有city
和job
参数:
def person(name, age, **kw):
if 'city' in kw:
# 有city参数
pass
if 'job' in kw:
# 有job参数
pass
print('name:', name, 'age:', age, 'other:', kw)
如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收city
和job
作为关键字参数。这种方式定义的函数如下:
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, *, 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中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数。
顺序:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。
例:
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)
在函数调用的时候,Python解释器自动按照参数位置和参数名把对应的参数传进去。
>>> 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)
的形式调用它,无论它的参数是如何定义的。
小结
要注意定义可变参数和关键字参数的语法:
*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})
。
递归函数
例:n! = 1 x 2 x 3 x ... x n
def fact(n):
if n==1:
return 1
return n * fact(n-1)
高级特性
切片
切片小结:
[ x : y : z ] : x 起始位置,y 终止位置(不包含),z 每z个取值
x为负则是倒数x个
x=0表示是第一个
x=-1表示是倒数第一个
例:
一个list如下:>>>L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack']
取前3个元素:
>>> [L[0], L[1], L[2]]
['Michael', 'Sarah', 'Tracy']
循环:
>>>r = []
>>>n=3
>>>for i in range(n):
r.append(L[i]) #append函数向尾部添加元素
>>>r
['Michael', 'Sarah', 'Tracy']
切片(Slice)操作符
>>>L[0:3]
['Michael', 'Sarah', 'Tracy']
也可以从索引1开始,取出2个元素出来:
>>> L[1:3]
['Sarah', 'Tracy']
支持L[-1]取倒数第一个元素,例:
>>> L[-2:]
['Bob', 'Jack']
>>> L[-2:-1]
['Bob']
倒数第一个元素的索引是-1
。
切片操作十分有用。我们先创建一个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个数,每两个取一个:
>>> L[:10:2]
[0, 2, 4, 6, 8]
所有数,每5个取一个:
>>> L[::5]
[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]
只写[:]就可以原样复制一个list:
>>> L[:]
[0, 1, 2, 3, ..., 99]
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,这种遍历称为迭代(Iteration)。
默认情况dict迭代key。如果要迭代value,用for value in d.values()
,同时迭代key和value,用for k, v in d.items()
。
判断一个对象是可迭代对象:
>>> from collections import Iterable
>>> isinstance('abc', Iterable) # str是否可迭代
True
>>> isinstance([1,2,3], Iterable) # list是否可迭代
True
>>> isinstance(123, Iterable) # 整数是否可迭代
False
对list实现下标循环:enumerate
函数
>>> for i, value in enumerate(['A', 'B', 'C']):
... print(i, value)
...
0 A
1 B
2 C
上面的for循环里,同时引用了两个变量,在Python里是很常见的,比如下面的代码:
>>> for x, y in [(1, 1), (2, 4), (3, 9)]:
... print(x, y)
...
1 1
2 4
3 9
列表生成式
例:生成list [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
>>> list(range(1, 11))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
生成[1x1, 2x2, 3x3, ..., 10x10]
:
>>>[x * x for x in range(1,11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
使用两层循环,可以生成全排列:
>>> [m + n for m in 'ABC' for n in 'XYZ']
['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']
for
循环可同时使用多个变量,如dict
的items()
可以同时迭代key和value:
>>> d = {'x': 'A', 'y': 'B', 'z': 'C' }
>>> for k, v in d.items():
... print(k, '=', v)
...
y = B
x = A
z = C
列表生成式使用两个变量来生成list:
>>> d = {'x': 'A', 'y': 'B', 'z': 'C' }
>>> [k + '=' + v for k, v in d.items()]
['y=B', 'x=A', 'z=C']
生成器
如果列表元素可以按照某种算法推算出来,可以在循环的过程中不断推算出后续的元素。
在Python中,这种一边循环一边计算的机制,称为生成器:generator。
创建一个generator:
1.把一个列表生成式的[]
改成()
:
>>> 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
at 0x1022ef630>
>>> 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 "", line 1, in
StopIteration
generator保存的是算法,每次调用next(g)
,就计算出g
的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration
的错误。
正确的方法是使用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
例:斐波那契数列
def fib(max):
n, a, b = 0, 0, 1
while n < max:
print(b)
a, b = b, a + b
n = n + 1
return 'done'
这种逻辑其实非常类似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
函数是顺序执行,遇到return
语句或者最后一行函数语句就返回。变成generator的函数,在每次调用next()
的时候执行,遇到yield
语句返回,再次执行时从上次返回的yield
语句处继续执行。
迭代器
凡是可作用于for
循环的对象都是Iterable
类型;
凡是可作用于next()
函数的对象都是Iterator
类型,它们表示一个惰性计算的序列;
集合数据类型如list
、dict
、str
等是Iterable
但不是Iterator
,不过可以通过iter()
函数获得一个Iterator
对象。
函数式编程
函数式编程——Functional Programming,虽然也可以归结到面向过程的程序设计,但其思想更接近数学计算。
高阶函数
map/reduce
map:
map()
函数接收两个参数,一个是函数,一个是Iterable
,map
将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator
返回。
例:f(x)=x2
>>> 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]
它把运算规则抽象了。
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
>>> from functools import reduce
>>> def fn(x, y):
... return x * 10 + y
...
>>> reduce(fn, [1, 3, 5, 7, 9])
13579
filter:筛选
filter()
也接收一个函数和一个序列。filter()
把传入的函数依次作用于每个元素,然后根据返回值是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]
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]
返回函数
函数作为返回值
例:返回求和函数
def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum
闭包
匿名函数
不显式地定义函数,直接传入匿名函数更方便。
例:f(x)=x2
>>> list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
[1, 4, 9, 16, 25, 36, 49, 64, 81]
关键字lambda
表示匿名函数,冒号前面的x
表示函数参数。
不用写return
,返回值就是该表达式的结果。
匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数:
>>> f = lambda x: x * x
>>> f
at 0x101c6ef28>
>>> f(5)
25
也可以把匿名函数作为返回值返回,比如:
def build(x, y):
return lambda: x * x + y * y
装饰器
函数是一个对象,函数对象可以被赋值给变量,通过变量也能调用该函数。
>>> def now():
... print('2015-3-25')
...
>>> f = now
>>> f()
2015-3-25
__name__
属性,可以拿到函数的名字:
>>> now.__name__
'now'
>>> f.__name__
'now'
假设我们要增强now()函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。
例:定义一个能打印日志的decorator
def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
助Python的@语法,把decorator置于函数的定义处:
@log
def now():
print('2015-3-25')
调用now()
函数,运行now()
函数本身,还会在运行now()
函数前打印一行日志:
>>> now()
call now():
2015-3-25
@log放到now()函数的定义处,相当于执行了语句:
now = log(now)
偏函数
Python的functools
模块提供了很多有用的功能,其中一个就是偏函数(Partial function)。
例:
int()
函数可以把字符串转换为整数,当仅传入字符串时,int()
函数默认按十进制转换:
>>> int('12345')
12345
int()函数还提供额外的base参数,默认值为10。如果传入base参数,就可以做N进制的转换:
>>> int('12345', base=8)
5349
>>> int('12345', 16)
74565
假设要转换大量的二进制字符串,每次都传入int(x, base=2)非常麻烦,所以可以定义一个int2()的函数,默认把base=2传进去:
def int2(x, base=2):
return int(x, base)
functools.partial
帮助我们创建一个偏函数,不需要自己定义int2()
,可以直接使用下面的代码创建一个新的函数int2:
>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64
>>> int2('1010101')
85
functools.partial
:把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。
模块
为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里。在Python中,一个.py文件就称之为一个模块(Module)。
模块是一组Python代码的集合,可以使用其他模块,也可以被其他模块使用。
创建自己的模块时,要注意:
1.模块名要遵循Python变量命名规范,不要使用中文、特殊字符;
2.模块名不要和系统模块名冲突,最好先查看系统是否已存在该模块,检查方法是在Python交互环境执行import abc,若成功则说明系统存在此模块。
使用模块
以内建的sys模块为例,编写一个hello的模块:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
' a test module '
__author__ = 'Michael Liao'
import sys
def test():
args = sys.argv
if len(args)==1:
print('Hello, world!')
elif len(args)==2:
print('Hello, %s!' % args[1])
else:
print('Too many arguments!')
if __name__=='__main__':
test()
第1行注释可以让这个hello.py文件直接在Unix/Linux/Mac上运行;
第2行注释表示.py文件本身使用标准UTF-8编码;
第4行是一个字符串,表示模块的文档注释,任何模块代码的第一个字符串都被视为模块的文档注释;
第6行使用__author__
变量把作者写进去;
以上就是Python模块的标准文件模板,当然也可以全部删掉不写。
使用sys
模块的第一步,就是导入该模块:
import sys
导入sys
模块后,变量sys
指向该模块,利用sys
,可以访问sys
模块的所有功能。
sys
模块有一个argv
变量,用list存储了命令行的所有参数。argv
至少有一个元素,因为第一个参数永远是该.py文件的名称,例如:
运行python3 hello.py
获得的sys.argv
就是['hello.py']
;
运行python3 hello.py Michael
获得的sys.argv
就是['hello.py', 'Michael']
。
最后,注意到这两行代码:
if __name__=='__main__':
test()
面向对象编程
类和实例
定义类:
class Student(object):
pass
class
后面紧接着是类名,即Student
,(object)
表示该类是从哪个类继承下来的,通常,如果没有合适的继承类,就使用object
类。
可以根据Student
类创建出Student
的实例,创建实例:
>>> bart = Student()
>>> bart
<__main__.Student object at 0x10a67a590>
>>> Student
可以自由地给一个实例变量绑定属性,比如,给实例bart
绑定一个name
属性:
>>> bart.name = 'Bart Simpson'
>>> bart.name
'Bart Simpson'
由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的__init__
方法,在创建实例的时候,就把name
,score
等属性绑上去:
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
注意到__init__
方法的第一个参数永远是self
,表示创建的实例本身,因此,在__init__
方法内部,就可以把各种属性绑定到self
,因为self
就指向创建的实例本身。
有了__init__
方法,在创建实例的时候,就不能传入空的参数了,必须传入与__init__
方法匹配的参数,但self
不需要传,Python解释器自己会把实例变量传进去:
>>> bart = Student('Bart Simpson', 59)
>>> bart.name
'Bart Simpson'
>>> bart.score
59
数据封装
在上面的Student
类中,每个实例就拥有各自的name
和score
这些数据。我们可以通过函数来访问这些数据,比如打印一个学生的成绩:
>>> def print_score(std):
... print('%s: %s' % (std.name, std.score))
...
>>> print_score(bart)
Bart Simpson: 59
可以直接在Student类的内部定义访问数据的函数——把“数据”给封装起来。这些封装数据的函数是和Student类本身是关联起来的,我们称之为类的方法:
class Student(object):
def __init__(self, name, score):
self.name = name
self.score = score
def print_score(self):
print('%s: %s' % (self.name, self.score))
感觉和c++的类差不多
访问限制
如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__
,实例的变量名如果以__
开头,就变成了一个私有变量,只有内部可以访问,外部不能访问:
class Student(object):
def __init__(self, name, score):
self.__name = name
self.__score = score
def print_score(self):
print('%s: %s' % (self.__name, self.__score))
继承和多态
比如,我们已经编写了一个名为Animal
的class,有一个run()
方法可以直接打印:
class Animal(object):
def run(self):
print('Animal is running...')
当我们需要编写Dog
和Cat
类时,就可以直接从Animal
类继承:
class Dog(Animal):
pass
class Cat(Animal):
pass
Animial
实现了run()
方法,Dog
和Cat
作为它的子类自动拥有run()
方法:
dog = Dog()
dog.run()
cat = Cat()
cat.run()
运行结果:
Animal is running...
Animal is running...
可以对子类增加一些方法,比如Dog类:
class Dog(Animal):
def run(self):
print('Dog is running...')
class Cat(Animal):
def run(self):
print('Cat is running...')
运行结果:
Dog is running...
Cat is running...
子类的run()
覆盖了父类的run()
,在代码运行的时候,总是会调用子类的run()
。这样,我们就获得了继承的另一个好处:多态。
判断一个变量是否是某个类型可以用isinstance()
判断:
>>> isinstance(a, list)
True
>>> isinstance(b, Animal)
True
>>> isinstance(c, Dog)
True
要理解多态的好处,我们还需要再编写一个函数,这个函数接受一个Animal类型的变量:
def run_twice(animal):
animal.run()
animal.run()
当我们传入Animal的实例时,run_twice()就打印出:
>>> run_twice(Animal())
Animal is running...
Animal is running...
当我们传入Dog的实例时,run_twice()就打印出:
>>> run_twice(Dog())
Dog is running...
Dog is running...
当我们传入Cat的实例时,run_twice()就打印出:
>>> run_twice(Cat())
Cat is running...
Cat is running...
看上去没啥意思,但是仔细想想,现在,如果我们再定义一个Tortoise类型,也从Animal派生:
class Tortoise(Animal):
def run(self):
print('Tortoise is running slowly...')
当我们调用run_twice()时,传入Tortoise的实例:
>>> run_twice(Tortoise())
Tortoise is running slowly...
Tortoise is running slowly...
任何依赖Animal
作为参数的函数或者方法都可以不加修改地正常运行,原因就在于多态。
获取对象信息
判断对象类型,使用type()
函数:
>>> type(123)
>>> type('str')
>>> type(None)
如果一个变量指向函数或者类,也可以用type()
判断:
>>> type(abs)
>>> type(a)
type()
函数返回对应的Class类型。
判断一个对象是否是函数怎么办:使用types模块中定义的常量
>>> import types
>>> def fn():
... pass
...
>>> type(fn)==types.FunctionType
True
>>> type(abs)==types.BuiltinFunctionType
True
>>> type(lambda x: x)==types.LambdaType
True
>>> type((x for x in range(10)))==types.GeneratorType
True
使用isinstance()
对于class的继承关系来说,使用type()
就很不方便。可以使用isinstance()函数。
如:object -> Animal -> Dog -> Husky
>>> isinstance(h, Husky)
True
isinstance()
判断的是一个对象是否是该类型本身,或者位于该类型的父继承链上。
能用type()
判断的基本类型也可以用isinstance()
判断:
>>> isinstance('a', str)
True
>>> isinstance(123, int)
True
>>> isinstance(b'a', bytes)
True
判断一个变量是否是某些类型中的一种,比如下面的代码就可以判断是否是list或者tuple:
>>> isinstance([1, 2, 3], (list, tuple))
True
>>> isinstance((1, 2, 3), (list, tuple))
True
dir()
获得一个对象的所有属性和方法:
>>> dir('ABC')
['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']
类似__xxx__
的属性和方法在Python中都是有特殊用途的,比如__len__
方法返回长度。在Python中,如果你调用len()
函数试图获取一个对象的长度,实际上,在len()
函数内部,它自动去调用该对象的__len__()
方法,所以,下面的代码是等价的:
>>> len('ABC')
3
>>> 'ABC'.__len__()
3
配合getattr()
、setattr()
以及hasattr()
,我们可以直接操作一个对象的状态:
>>> class MyObject(object):
... def __init__(self):
... self.x = 9
... def power(self):
... return self.x * self.x
...
>>> obj = MyObject()
测试该对象的属性:
>>> hasattr(obj, 'x') # 有属性'x'吗?
True
>>> obj.x
9
>>> hasattr(obj, 'y') # 有属性'y'吗?
False
>>> setattr(obj, 'y', 19) # 设置一个属性'y'
>>> hasattr(obj, 'y') # 有属性'y'吗?
True
>>> getattr(obj, 'y') # 获取属性'y'
19
>>> obj.y # 获取属性'y'
19
如果试图获取不存在的属性,会抛出AttributeError的错误:
>>> getattr(obj, 'z') # 获取属性'z'
Traceback (most recent call last):
File "", line 1, in
AttributeError: 'MyObject' object has no attribute 'z'
可以传入一个default参数,如果属性不存在,就返回默认值:
>>> getattr(obj, 'z', 404) # 获取属性'z',如果不存在,返回默认值404
404
实例属性和类属性
Python是动态语言,根据类创建的实例可以任意绑定属性。
给实例绑定属性的方法是通过实例变量,或者通过self
变量:
class Student(object):
def __init__(self, name):
self.name = name
s = Student('Bob')
s.score = 90
类属性,归Student
类所有:
class Student(object):
name = 'Student'
面向对象高级编程
使用slots
先定义class:
class Student(object):
pass
给实例绑定一个属性:
>>> s = Student()
>>> s.name = 'Michael' # 动态给实例绑定一个属性
>>> print(s.name)
Michael
给实例绑定一个方法:
>>> def set_age(self, age): # 定义一个函数作为实例方法
... self.age = age
...
>>> from types import MethodType
>>> s.set_age = MethodType(set_age, s) # 给实例绑定一个方法
>>> s.set_age(25) # 调用实例方法
>>> s.age # 测试结果
25
但是,给一个实例绑定的方法,对另一个实例是不起作用的。
为了给所有实例都绑定方法,可以给class绑定方法:
>>> def set_score(self, score):
... self.score = score
...
>>> Student.set_score = set_score
如果我们想要限制实例的属性怎么办?比如,只允许对Student实例添加name
和age
属性。
定义一个特殊的__slots__
变量,来限制该class实例能添加的属性:
class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
:
>>> s = Student() # 创建新的实例
>>> s.name = 'Michael' # 绑定属性'name'
>>> s.age = 25 # 绑定属性'age'
>>> s.score = 99 # 绑定属性'score'
Traceback (most recent call last):
File "", line 1, in
AttributeError: 'Student' object has no attribute 'score'
由于score
没有被放到__slots__
中,所以不能绑定score属性,试图绑定score将得到AttributeError的错误。
使用__slots__
要注意,__slots__
定义的属性仅对当前类实例起作用,对继承的子类是不起作用的:
>>> class GraduateStudent(Student):
... pass
...
>>> g = GraduateStudent()
>>> g.score = 9999
使用@property
多重继承
于需要Runnable
功能的动物,就多继承一个Runnable
,例如Dog
:
class Dog(Mammal, Runnable):
pass
对于需要Flyable
功能的动物,就多继承一个Flyable
,例如Bat
:
class Bat(Mammal, Flyable):
pass
通过多重继承,一个子类就可以同时获得多个父类的所有功能。
定制类
___str___
定义一个Student
类,打印一个实例:
>>> class Student(object):
... def __init__(self, name):
... self.name = name
...
>>> print(Student('Michael'))
<__main__.Student object at 0x109afb190>
<__main__.Student object at 0x109afb190>
并不好看。
定义好__str__()
方法,返回一个好看的字符串就可以了:
>>> class Student(object):
... def __init__(self, name):
... self.name = name
... def __str__(self):
... return 'Student object (name: %s)' % self.name
...
>>> print(Student('Michael'))
Student object (name: Michael)
直接敲变量不用print,打印出来的实例还是不好看:
>>> s = Student('Michael')
>>> s
<__main__.Student object at 0x109afb310>
因为直接显示变量调用的不是__str__()
,而是__repr__()
,两者的区别是__str__()
返回用户看到的字符串,而__repr__()
返回程序开发者看到的字符串。
解决办法是再定义一个__repr__()
。简便方法:
class Student(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Student object (name=%s)' % self.name
__repr__ = __str__
__iter__
一个类被用于for ... in循环,类似list或tuple那样,就必须实现一个__iter__()
方法。
Python的for循环就会不断调用该迭代对象的__next__()
方法拿到循环的下一个值,直到遇到StopIteration
错误时退出循环。
class Fib(object):
def __init__(self):
self.a, self.b = 0, 1 # 初始化两个计数器a,b
def __iter__(self):
return self # 实例本身就是迭代对象,故返回自己
def __next__(self):
self.a, self.b = self.b, self.a + self.b # 计算下一个值
if self.a > 100000: # 退出循环的条件
raise StopIteration()
return self.a # 返回下一个值
__getitem__
要表现得像list那样按照下标取出元素,需要实现__getitem__()
方法:
class Fib(object):
def __getitem__(self, n):
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
__getattr__
要避免这个错误,除了可以加上一个score
属性外,Python还有另一个机制,那就是写一个__getattr__()
方法,动态返回一个属性。修改如下:
class Student(object):
def __init__(self):
self.name = 'Michael'
def __getattr__(self, attr):
if attr=='score':
return 99
当调用不存在的属性时,比如score
,Python解释器会试图调用__getattr__(self, 'score')
来尝试获得属性,这样,我们就有机会返回score的值:
>>> s = Student()
>>> s.name
'Michael'
>>> s.score
99
返回函数也是完全可以的:
class Student(object):
def __getattr__(self, attr):
if attr=='age':
return lambda: 25
只是调用方式要变为:
>>> s.age()
25
注意,只有在没有找到属性的情况下,才调用__getattr__
,已有的属性,比如name
,不会在__getattr__
中查找。
__call__
定义一个__call__()
方法,可以直接对实例进行调用:
class Student(object):
def __init__(self, name):
self.name = name
def __call__(self):
print('My name is %s.' % self.name)
调用方式:
>>> s = Student('Michael')
>>> s() # self参数不要传入
My name is Michael.
使用枚举类
为这样的枚举类型定义一个class类型,然后,每个常量都是class的一个唯一实例。Python提供了Enum
类来实现这个功能:
from enum import Enum
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
使用元类
错误、调试和测试(异常处理)
try/except语句。
try/except语句用来检测try语句块中的错误,从而让except语句捕获异常信息并处理:
try:
<语句> #运行别的代码
except <名字>:
<语句> #如果在try部份引发了'name'异常
except <名字>,<数据>:
<语句> #如果引发了'name'异常,获得附加的数据
else:
<语句> #如果没有异常发生
实例:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
try:
fh = open("testfile", "w")
fh.write("这是一个测试文件,用于测试异常!!")
except IOError:
print "Error: 没有找到文件或读取文件失败"
else:
print "内容写入文件成功"
fh.close()
结果:
$ python test.py
内容写入文件成功
$ cat testfile # 查看写入的内容
这是一个测试文件,用于测试异常!!
你可以不带任何异常类型使用except:
try:
正常的操作
......................
except:
发生异常,执行这块代码
......................
else:
如果没有异常执行这块代码
使用except而带多种异常类型:
try:
正常的操作
......................
except(Exception1[, Exception2[,...ExceptionN]]]):
发生以上多个异常中的一个,执行这块代码
......................
else:
如果没有异常执行这块代码
try-finally 语句
try-finally 语句无论是否发生异常都将执行最后的代码。
try:
<语句>
finally:
<语句> #退出try时总会执行
raise
实例:
#!/usr/bin/python
# -*- coding: UTF-8 -*-
try:
fh = open("testfile", "w")
fh.write("这是一个测试文件,用于测试异常!!")
finally:
print "Error: 没有找到文件或读取文件失败"
如果打开的文件没有可写权限,输出如下所示:
$ python test.py
Error: 没有找到文件或读取文件失败
当在try块中抛出一个异常,立即执行finally块代码。
IO编程
文件读写
读文件
以读文件的模式打开一个文件对象,使用open()
函数,传入文件名和标示符:
f = open('/Users/michael/test.txt', 'r')
标示符'r'表示读。
如果文件不存在,open()函数就会抛出一个IOError
的错误,并且给出错误码和详细的信息告诉你文件不存在。
如果文件打开成功,调用read()
方法可以一次读取文件的全部内容,Python把内容读到内存,用一个str
对象表示:
>>> f.read()
'Hello, world!'
最后一步是调用close()
方法关闭文件:
>>> f.close()
由于文件读写时都有可能产生IOError
,一旦出错,后面的f.close()
就不会调用。所以,为了保证无论是否出错都能正确地关闭文件,我们可以使用try ... finally
来实现:
#try-finally 语句无论是否发生异常都将执行最后的代码。
try:
f = open('/path/to/file', 'r')
print(f.read())
finally:
if f:
f.close()
Python引入了with
语句来自动帮我们调用close()
方法:
with open('/path/to/file', 'r') as f:
print(f.read())
调用read()
会一次性读取文件的全部内容。
反复调用read(size)
,每次最多读取size个字节的内容。
调用readline()
可以每次读取一行内容。
调用readlines()
一次读取所有内容并按行返回list。
如果是配置文件,调用readlines()
最方便:
for line in f.readlines():
print(line.strip()) # 把末尾的'\n'删掉
file-like Object
一个概念:
像open()
函数返回的这种有个read()
方法的对象,在Python中统称为file-like Object。除了file外,还可以是内存的字节流,网络流,自定义流等等。file-like Object不要求从特定类继承,只要写个read()
方法就行。
StringIO
就是在内存中创建的file-like Object,常用作临时缓冲。
二进制文件
要读取二进制文件,比如图片、视频等等,用'rb'模式打开文件:
>>> f = open('/Users/michael/test.jpg', 'rb')
>>> f.read()
b'\xff\xd8\xff\xe1\x00\x18Exif\x00\x00...' # 十六进制表示的字节
字符编码
读取非UTF-8编码的文本文件,需要给open()
函数传入encoding
参数,例如,读取GBK编码的文件:
>>> f = open('/Users/michael/gbk.txt', 'r', encoding='gbk')
>>> f.read()
'测试'
遇到有些编码不规范的文件,你可能会遇到UnicodeDecodeError
,因为在文本文件中可能夹杂了一些非法编码的字符。遇到这种情况,open()
函数还接收一个errors
参数,表示如果遇到编码错误后如何处理。最简单的方式是直接忽略:
>>> f = open('/Users/michael/gbk.txt', 'r', encoding='gbk', errors='ignore')
写文件
调用open()
函数时,传入标识符'w'或者'wb'表示写文本文件或写二进制文件:
>>> f = open('/Users/michael/test.txt', 'w')
>>> f.write('Hello, world!')
>>> f.close()
可以反复调用write()
来写入文件,但要调用f.close()
来关闭文件。
写文件时,操作系统往往不会立刻把数据写入磁盘,而是放到内存缓存起来,空闲时再写入。调用close()
方法时,操作系统才保证把没有写入的数据全部写入磁盘。
如果记调用close()
,数据可能只写了一部分到磁盘,剩下的丢失了。
with open('/Users/michael/test.txt', 'w') as f:
f.write('Hello, world!')
#with:不管with中的代码出现什么错误,都会进行对当前对象进行清理工作。
要写入特定编码的文本文件,给open()
函数传入encoding
参数,将字符串自动转换成指定编码。
以'w'
模式写入文件时,如果文件已存在,会直接覆盖。
可以传入'a'以追加(append)模式写入追加到文件末尾。
StringIO和BytesIO
StringIO是在内存中读写str。
要把str写入StringIO,我们需要先创建一个StringIO,然后,像文件一样写入即可:
>>> from io import StringIO
>>> f = StringIO()
>>> f.write('hello')
5
>>> f.write(' ')
1
>>> f.write('world!')
6
>>> print(f.getvalue())
hello world!
getvalue()
用于获得写入后的str。
要读取StringIO,可以用一个str初始化StringIO,像读文件一样读取:
>>> from io import StringIO
>>> f = StringIO('Hello!\nHi!\nGoodbye!')
>>> while True:
... s = f.readline()
... if s == '':
... break
... print(s.strip())
...
Hello!
Hi!
Goodbye!
操作二进制数据使用BytesIO。
BytesIO实现了在内存中读写bytes,创建一个BytesIO,然后写入一些bytes:
>>> from io import BytesIO
>>> f = BytesIO()
>>> f.write('中文'.encode('utf-8'))
6
>>> print(f.getvalue())
b'\xe4\xb8\xad\xe6\x96\x87'
请注意,写入的不是str,而是经过UTF-8编码的bytes。
和StringIO类似,可以用一个bytes初始化BytesIO,再像读文件一样读取:
>>> from io import BytesIO
>>> f = BytesIO(b'\xe4\xb8\xad\xe6\x96\x87')
>>> f.read()
b'\xe4\xb8\xad\xe6\x96\x87'
操作文件和目录
Python内置的os
模块可以直接调用操作系统提供的接口函数。
打开Python交互式命令行,使用os
模块的基本功能:
>>> import os
>>> os.name # 操作系统类型
'posix'
如果是posix
,说明系统是Linux、Unix或Mac OS X,如果是nt
,就是Windows系统。
获取详细的系统信息调用uname()
:
>>> os.uname()
posix.uname_result(sysname='Darwin', nodename='MichaelMacPro.local', release='14.3.0', version='Darwin Kernel Version 14.3.0: Mon Mar 23 11:59:05 PDT 2015; root:xnu-2782.20.48~5/RELEASE_X86_64', machine='x86_64')
uname()
函数在Windows上不提供!!!(不早说)
所有说,os
模块的某些函数是跟操作系统相关的。
环境变量
在操作系统中定义的环境变量,全部保存在os.environ
这个变量中,可以直接查看:
>>> os.environ
要获取某个环境变量的值,可以调用os.environ.get('key')
:
>>> os.environ.get('PATH')
'/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/opt/X11/bin:/usr/local/mysql/bin'
>>> os.environ.get('x', 'default')
'default'
操作文件和目录
操作文件和目录的函数一部分放在os模块中,一部分放在os.path模块中。
查看、创建和删除目录可以这么调用:
# 查看当前目录的绝对路径:
>>> os.path.abspath('.')
'/Users/michael'
# 在某个目录下创建一个新目录,首先把新目录的完整路径表示出来:
>>> os.path.join('/Users/michael', 'testdir')
'/Users/michael/testdir'
# 然后创建一个目录:
>>> os.mkdir('/Users/michael/testdir')
# 删掉一个目录:
>>> os.rmdir('/Users/michael/testdir')
把两个路径合成一个要通过os.path.join()
,这样可以正确处理不同操作系统的路径分隔符。
在Linux/Unix/Mac下,os.path.join()
返回这样的字符串:
part-1/part-2
在Windows下返回这样的字符串:
part-1\part-2
拆分路径要通过os.path.split()
,这样可以把一个路径拆分为两部分,后一部分总是最后级别的目录或文件名:
>>> os.path.split('/Users/michael/testdir/file.txt')
('/Users/michael/testdir', 'file.txt')
os.path.splitext()
可以直接让你得到文件扩展名:
>>> os.path.splitext('/path/to/file.txt')
('/path/to/file', '.txt')
文件操作使用下面的函数。假定当前目录下有一个test.txt文件:
# 对文件重命名:
>>> os.rename('test.txt', 'test.py')
# 删掉文件:
>>> os.remove('test.py')
复制文件的函数在os模块中不存在!因为复制文件并非由操作系统提供的系统调用。
理论上讲,通过读写文件可以完成文件复制,不过要多写很多代码。
shutil
模块提供了copyfile()
的函数。(以看做是os
模块的补充。)
利用Python的特性来过滤文件。
比如我们要列出当前目录下的所有目录:
>>> [x for x in os.listdir('.') if os.path.isdir(x)]
['.lein', '.local', '.m2', '.npm', '.ssh', '.Trash', '.vim', 'Applications', 'Desktop', ...]
要列出所有的.py文件:
>>> [x for x in os.listdir('.') if os.path.isfile(x) and os.path.splitext(x)[1]=='.py']
['apis.py', 'config.py', 'models.py', 'pymonitor.py', 'test_db.py', 'urls.py', 'wsgiapp.py']
未解决的问题:啥是dir
,cp
序列化
在程序运行的过程中,所有的变量都是在内存中的。如定义一个dict:
d = dict(name='Bob', age=20, score=88)
把name
改成'Bill'
,但程序结束,变量所占用的内存就被操作系统全部回收。如果没有把修改后的'Bill'
存储到磁盘上,下次重新运行程序,变量又被初始化为'Bob'
。
我们把变量从内存中变成可存储或传输的过程称之为序列化,在Python中叫pickling。
序列化之后,就可以把序列化后的内容写入磁盘,或者通过网络传输到别的机器上。
反过来,把变量内容从序列化的对象重新读到内存里称之为反序列化,即unpickling。
Python提供了pickle
模块来实现序列化。
首先,把一个对象序列化并写入文件:
>>> import pickle
>>> d = dict(name='Bob', age=20, score=88)
>>> pickle.dumps(d)
b'\x80\x03}q\x00(X\x03\x00\x00\x00ageq\x01K\x14X\x05\x00\x00\x00scoreq\x02KXX\x04\x00\x00\x00nameq\x03X\x03\x00\x00\x00Bobq\x04u.'
pickle.dumps()
方法把任意对象序列化成一个bytes
,然后,就可以把这个bytes
写入文件。
或者用pickle.dump()
直接把对象序列化后写入一个file-like Object:
>>> f = open('dump.txt', 'wb')
>>> pickle.dump(d, f)
>>> f.close()
当要把对象从磁盘读到内存时,可以先把内容读到一个bytes,然后用pickle.loads()反序列化出对象,也可以直接用pickle.load()方法从一个file-like Object中直接反序列化出对象。
打开另一个Python命令行来反序列化刚才保存的对象:
>>> f = open('dump.txt', 'rb')
>>> d = pickle.load(f)
>>> f.close()
>>> d
{'age': 20, 'score': 88, 'name': 'Bob'}
JSON
如果我们要在不同的编程语言之间传递对象,就必须把对象序列化为标准格式:序列化为JSON,因为JSON表示出来就是一个字符串,可以被所有语言读取,也可以方便地存储到磁盘或者通过网络传输。JSON是标准格式,可以直接在Web页面中读取。
Python内置的json模块提供了Python对象到JSON格式的转换:
>>> import json
>>> d = dict(name='Bob', age=20, score=88)
>>> json.dumps(d)
'{"age": 20, "score": 88, "name": "Bob"}'
dumps()
方法返回一个str,内容就是标准的JSON。类似的,dump()方法可以直接把JSON写入一个file-like Object
。
进程和线程
现代操作系统比如Mac OS X,UNIX,Linux,Windows等,都是支持“多任务”的操作系统。
多任务:简单地说,就是操作系统可以同时运行多个任务。
单核CPU执行多任务:操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,再切换到任务3,执行0.01秒……
对于操作系统来说,一个任务就是一个进程(Process)。
进程内的这些“子任务”为线程(Thread)。
多任务的实现有3种方式:
· 多进程模式
· 多线程模式
· 多进程+多线程模式
多进程
访问数据库
数据库类别:
付费的商用数据库:
· Oracle,典型的高富帅;
· SQL Server,微软自家产品,Windows定制专款;
· DB2,IBM的产品,听起来挺高端;
· Sybase,曾经跟微软是好基友,后来关系破裂,现在家境惨淡。
这些数据库都是不开源而且付费的。
免费的开源数据库:
· MySQL,常用
· PostgreSQL,学术气息有点重
· sqlite,嵌入式数据库,适合桌面和移动应用。
web开发
HTTP协议简介
在Web应用中,服务器把网页传给浏览器,实际上就是把网页的HTML代码发送给浏览器,让浏览器显示出来。
览器和服务器之间的传输协议是HTTP:
· TML是一种用来定义网页的文本,会HTML,就可以编写网页;
· HTTP是在网络上传输HTML的协议,用于浏览器和服务器的通信。
F12打开开发者工具。
Elements
显示网页的结构,Network
显示浏览器和服务器的通信。
HTTP请求流程:
步骤1:浏览器首先向服务器发送HTTP请求,请求包括:
方法:GET
还是POST
,GET
仅请求资源,POST
会附带用户数据;
路径:/full/url/path
;
域名:由Host头指定:Host: www.sina.com.cn
以及其他相关的Header;
如果是POST,那么请求还包括一个Body,包含用户数据。
步骤2:服务器向浏览器返回HTTP响应,响应包括:
响应代码:200
表示成功,3xx
表示重定向,4xx
表示客户端发送的请求有错误,5xx
表示服务器端处理时发生了错误;
响应类型:由Content-Type
指定,例如:Content-Type: text/html;charset=utf-8
表示响应类型是HTML文本,并且编码是UTF-8
,Content-Type: image/jpeg
表示响应类型是JPEG格式的图片;
以及其他相关的Header;
通常服务器的HTTP响应会携带内容,也就是有一个Body,包含响应的内容,网页的HTML源码就在Body中。
步骤3:如果浏览器还需要继续向服务器请求其他资源,比如图片,就再次发出HTTP请求,重复步骤1、2。
一个HTTP请求只处理一个资源。
HTTP协议同时具备极强的扩展性,在HTML中可以链入其他服务器的资源,从而将请求压力分散到各个服务器上,并且,一个站点可以链接到其他站点,无数个站点互相链接起来,形成World Wide WebWWW)。
HTTP格式:
一个HTTP包含Header和Body两部分,其中Body是可选的。
HTTP GET请求的格式:
GET /path HTTP/1.1
Header1: Value1
Header2: Value2
Header3: Value3
每个Header一行一个,换行符是\r\n
。
HTTP POST请求的格式:
POST /path HTTP/1.1
Header1: Value1
Header2: Value2
Header3: Value3
body data goes here...
当遇到连续两个\r\n
时,Header部分结束,后面的数据全部是Body。
HTTP响应的格式:
200 OK
Header1: Value1
Header2: Value2
Header3: Value3
body data goes here...
**HTML简介
HTML文档就是一系列的Tag组成,最外层的Tag是。规范的HTML也包含
...
和...)
,HTML是富文档模型,还有一系列的Tag用来表示链接、图片、表格、表单等等。
CSS简介
CSS用来控制HTML里的所有元素如何展现。
JavaScript简介
为了让HTML具有交互性而作为脚本语言添加的,JavaScript既可以内嵌到HTML中,也可以从外部链接到HTML中。
**WSGI接口
Web应用的本质就是:
1.浏览器发送一个HTTP请求;
2.服务器收到请求,生成一个HTML文档;
3.服务器把HTML文档作为HTTP响应的Body发送给浏览器;
4.浏览器收到HTTP响应,从HTTP Body取出HTML文档并显示。
动态生成HTML,底层代码由专门的服务器软件实现,用Python专注于生成HTML文档。因为我们不希望接触到TCP连接、HTTP原始请求和响应格式,所以,需要一个统一的接口,让我们专心用Python编写Web业务。
这个接口就是WSGI:Web Server Gateway Interface。
WSGI接口定义非常简单,它只要求Web开发者实现一个函数,就可以响应HTTP请求。我们来看一个最简单的Web版本的“Hello, web!”:
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return [b'Hello, web!
']
无论多么复杂的Web应用程序,入口都是一个WSGI处理函数。HTTP请求的所有输入信息都可以通过environ
获得,HTTP响应的输出都可以通过start_response()
加上函数返回值作为Body。
使用Web框架
有了Web框架,我们在编写Web应用时,注意力就从WSGI处理函数转移到URL+对应的处理函数。
在编写URL处理函数时,除了配置URL外,从HTTP请求拿到用户数据也是非常重要的。Web框架都提供了自己的API来实现这些功能。Flask通过request.form['name']
来获取表单的内容。
异步IO
协程
协程,又称微线程,纤程。英文名Coroutine。
子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用了C,C执行完毕返回,B执行完毕返回,最后是A执行完毕。
子程序调用是通过栈实现的,一个线程就是执行一个子程序。
子程序调用总是一个入口,一次返回,调用顺序是明确的。
协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。
比如子程序A、B:
def A():
print('1')
print('2')
print('3')
def B():
print('x')
print('y')
print('z')
假设由协程执行,在执行A的过程中,可以随时中断,去执行B,B也可能在执行过程中中断再去执行A,结果可能是:
1
2
x
y
3
z
协程有何优势?
最大的优势就是协程极高的执行效率。没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。
第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就行。
Python对协程的支持是通过generator实现的。
在generator中:
1.通过for循环来迭代
2.不断调用next()函数获取由yield语句返回的下一个值。