python
基础
首先记录pycharm的一些快捷键:
shift
+F10
:运行py文件CTRL
:查看类或函数的使用方法CTRL
+/
进行注释CTRL
+P
查看当前函数所需要的参数CTRL
+D
复制上一行的代码CTRL
+C
退出程序
python的IO
- 输出:
print()
,例如print('hello world')
,print()
可以接受多个字符串,字符串之间用,
隔开,就可以将字符串连成一串输出。print()
会依次打印各个字符串,遇到,
就会输出一个空格。 - 输入:
input()
,例如name=input()
。input()
有一个字符串参数,例如name = input('please enter your name: ')
,input的返回值类型是str,会首先输出字符串参数的内容。
python的数据类型
整数:为了清楚,可以在数字中间以
_
分隔,例如10_000_000_000
和10000000000
是一样的。字符串:以
'
或"
括起来的任意文本。如果'
括起来内部还有'
这种的话,就要加转义字符\
。python可以用r''
表示''
内部的字符串不转义。布尔值:只有
True
和False
两种值。可以直接用True
和False
表示布尔值,也可以通过布尔运算计算出来。布尔值可以用and
、or
和not
运算。空值:空值用
None
表示,不能理解为0
,0
是有意义的,而None
是一个特殊的空值。变量:python是动态语言,可以把任意数据类型赋值给变量,同一个变量可以反复赋值,而且可以是不同类型的变量。
常量:常用大写变量名表示常量,但是这个常量仍然是一个变量,因为python没有任何机制能够保证常量是常量,只是我们自己心里的一个默认罢了。
list
列表:list
是一种有序的集合,可以随时添加和删除其中的元素。例如:'Michael', 'Bob', 'Tracy'] classmates = [
classmates
['Michael', 'Bob', 'Tracy']变量
classmates
就是一个list。用len()
函数可以获得list元素的个数。len(classmates)
3用索引来访问list中每一个位置的元素,记得索引是从
0
开始的,最后一个元素的索引是len(classmates) - 1
。如果要取最后一个元素,除了计算索引位置外,还可以用-1
做索引,直接获取最后一个元素,以此类推,可以获取倒数第2个、倒数第3个。当索引超出了范围时,Python会报一个IndexError
错误。0] classmates[
'Michael'
1] classmates[
'Bob'
2] classmates[
'Tracy'
3] classmates[
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
1] classmates[-
'Tracy'
2] classmates[-
'Bob'
3] classmates[-
'Michael'
4] classmates[-
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of rangelist是一个可变的有序表,所以,可以往list中追加元素到末尾。
'Adam') classmates.append(
classmates
['Michael', 'Bob', 'Tracy', 'Adam']也可以把元素插入到指定的位置,比如索引号为
1
的位置。1, 'Jack') classmates.insert(
classmates
['Michael', 'Jack', 'Bob', 'Tracy', 'Adam']要删除list末尾的元素,用
pop()
方法。classmates.pop()
'Adam'
classmates
['Michael', 'Jack', 'Bob', 'Tracy']要删除指定位置的元素,用
pop(i)
方法,其中i
是索引位置。1) classmates.pop(
'Jack'
classmates
['Michael', 'Bob', 'Tracy']要把某个元素替换成别的元素,可以直接赋值给对应的索引位置。
1] = 'Sarah' classmates[
classmates
['Michael', 'Sarah', 'Tracy']list里面的元素的数据类型也可以不同。
'Apple', 123, True] L = [
list元素也可以是另一个list。
'python', 'java', ['asp', 'php'], 'scheme'] s = [
len(s)
4要注意
s
只有4个元素,其中s[2]
又是一个list,如果拆开写就更容易理解了:'asp', 'php'] p = [
'python', 'java', p, 'scheme'] s = [要拿到
'php'
可以写p[1]
或者s[2][1]
,因此s
可以看成是一个二维数组。如果一个list中一个元素也没有,就是一个空的list,它的长度为0。
L = []
len(L)
0
tuple:另一种有序列表叫元组:tuple。tuple和list非常类似,但是tuple一旦初始化就不能修改。
>>> classmates = ('Michael', 'Bob', 'Tracy')
classmates这个tuple不能变了,它也没有append(),insert()这样的方法。其他获取元素的方法和list是一样的,你可以正常地使用`classmates[0]`,`classmates[-1]`,但不能赋值成另外的元素。
- 当你定义一个tuple时,在定义的时候,tuple的元素就必须被确定下来。
```python
>>> t = (1, 2)
>>> t
(1, 2)如果要定义一个空的tuple,可以写成
()
t = ()
t
()但是,要定义一个只有1个元素的tuple,如果这么定义:
1) t = (
t
1定义的不是tuple,是
1
这个数!这是因为括号()
既可以表示tuple,又可以表示数学公式中的小括号,这就产生了歧义,因此,Python规定,这种情况下,按小括号进行计算,计算结果自然是1
。所以,只有1个元素的tuple定义时必须加一个逗号,
,来消除歧义:1,) t = (
t
(1,)Python在显示只有1个元素的tuple时,也会加一个逗号
,
。一个”可变的“tuple:
'a', 'b', ['A', 'B']) t = (
2][0] = 'X' t[
2][1] = 'Y' t[
t
('a', 'b', ['X', 'Y'])这个tuple定义的时候有3个元素,分别是
'a'
,'b'
和一个list。
当我们把list的元素'A'
和'B'
修改为'X'
和'Y'
后,tuple变为:
表面上看,tuple的元素确实变了,但其实变的不是tuple的元素,而是list的元素。tuple一开始指向的list并没有改成别的list,所以,tuple所谓的“不变”是说,tuple的每个元素,指向永远不变。即向'a'
,就不能改成指向'b'
,指向一个list,就不能改成指向其他对象,但指向的这个list本身是可变的。
Python的整数没有大小限制,Python的浮点数也没有大小限制,但是超出一定范围就直接表示为inf
(无限大)。
- 类型转换例如:int(str)
python的计算
- 一种除法是
/
,/
计算结果是浮点数。 - 还有一种除法是
//
,两个整数的除法仍然是整数。 - 取余
%
字符编码
python的字符串以Unicode编码,对于单个字符编码,ord()
函数获取字符的整数表示,chr()
函数把编码转换为对应的字符。python对bytes
类型的数据用带b前缀的单引号或双引号表示,例如x=b'ABC'
。以Unicode表示的str
通过encode()
方法可以编码为指定的bytes
。要把bytes
变为str
,就需要用decode()
方法。要计算str
包含多少个字符,可以用len()
函数,len()
函数计算的是str
的字符数,如果换成bytes
,len()
函数就计算字节数。
格式化字符串
python使用%
格式化字符串,有几个%?
占位符,后面就跟着几个变量或者值,如果只有一个%?
,括号可以省略。例如:
'Hello, %s' % 'world' |
常见占位符:
占位符 | 替换内容 |
---|---|
%d | 整数 |
%f | 浮点数 |
%s | 字符串 |
%x | 十六进制整数 |
如果你不太确定应该用什么,%s
永远起作用,它会把任何数据类型转换为字符串。有些时候,字符串里面的%
是一个普通字符怎么办?这个时候就需要转义,用%%
来表示一个%
。
另一种格式化字符串的方法是使用字符串的format()
方法,它会用传入的参数依次替换字符串内的占位符{0}
、{1}
……,不过这种方式写起来比%要麻烦得多:
'Hello, {0}, 成绩提升了 {1:.1f}%'.format('小明', 17.125) |
最后一种格式化字符串的方法是使用以f
开头的字符串,称之为f-string
,它和普通字符串不同之处在于,字符串如果包含{xxx}
,就会以对应的变量替换:
2.5 r = |
上述代码中,{r}
被变量r
的值替换,{s:.2f}
被变量s
的值替换,并且:
后面的.2f
指定了格式化参数(即保留两位小数),因此,{s:.2f}
的替换结果是19.62
。
If相关语句
python中的if语句通过缩进表明后面的语句是同一个代码块的,这点和其他语言不同,并且要在条件之后加上一个冒号。例如:
age = 3
if age >= 18:
print('your age is', age)
print('adult')
else:
print('your age is', age)
print('teenager')python中的else-if语句写法如,这里elif就是else if的缩写:
age = 3
if age >= 18:
print('adult')
elif age >= 6:
print('teenager')
else:
print('kid')
循环语句
python循环有两种。
for…in循环,依次把list或tuple中的每个元素迭代出来.
names = ['Michael', 'Bob', 'Tracy']
for name in names:
print(name)for x in...
循环就是把每个元素代入变量x,然后执行缩进块的语句,如下例子较为麻烦。sum = 0
for x in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]:
sum = sum + x
print(sum)python有
range()
函数,可以生成整数序列,再通过list()
函数可以转换为listsum = 0
for x in range(101): #这是从0-100,也可以写成list(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语句,用法与其他语言一致
n = 0
while n < 10:
n = n + 1
if n % 2 == 0: # 如果n是偶数,执行continue语句
continue # continue语句会直接继续下一轮循环,后续的print()语句不会执行
print(n)
dict和set
字典dict其实就是map,是key-value的映射,特点是查找速度快。dict的key是不可变对象如字符串、整数,list是可变的
例子:
# 初始化,将数据放入dict |
判断key是否存在
通过
in
判断'Thomas' in d
False通过dict提供的
get()
方法,如果key不存在,可以返回None
,或者自己指定的value:'Thomas') d.get(
'Thomas', -1) # 指定不存在的话value=-1 d.get(
-1
删除key,用pop(key)方法,对应的value也会从dict中删除
'Bob') d.pop( |
set也是一组key的集合,但是不存储value,key不能重复,同样不能放入可变对象。
创建一个set,需要提供list作为输入集合。
set([1, 2, 3]) s = |
传入的参数[1, 2, 3]
是一个list,而显示的{1, 2, 3}
只是告诉你这个set内部有1,2,3这3个元素,显示的顺序也不表示set是有序的。
通过add(key)
方法可以添加元素到set中,可以重复添加,但不会有效果;通过remove(key)
方法可以删除元素;set可以看成数学意义上的无序和无重复元素的集合,因此,两个set可以做数学意义上的交集、并集等操作
4) s.add( |
函数
调用函数
help(函数名)
可以查看函数信息
abs函数是绝对值函数,max函数是最大值函数,它们都是内置函数。若传入的参数书量不对会报TypeError错误,如果传入的参数数量是对的,但是参数类型错误,也会报TypeError错误。
在python中数据类型转换是一种函数,可以直接调用,它是内置的,例如:
int('123') |
函数名其实就是指向一个函数对象的引用,完全可以把函数名赋给一个变量,相当于给这个函数起了一个“别名”,例如:
abs # 变量a指向abs函数 a = |
定义函数
python中定义函数需要使用def语句
,依次写出函数名、括号、括号中的参数和冒号:
,然后在缩进块中编写函数体,函数的返回值用return语句返回。
函数一旦执行到return,就执行完毕了,将结果返回。没有return语句,函数执行完毕后也会返回结果,只是结果为None
。return None
可以简写为return。
导入函数,可以举例用from abstest import my_abs
来导入my_abs()
函数,注意abstest
是文件名。
空函数:如果想定义一个什么事也不做的空函数,可以用pass语句。pass
可以用来作为占位符,比如现在还没想好怎么写函数的代码,就可以先放一个pass
,让代码能运行起来。pass还可以用在其他语句里,比如:
if age >= 18: |
对于函数的传入参数检查,可以使用内置函数isinstance实现:
def my_abs(x): |
python中的函数可以返回多个值:
import math |
import math
语句表示导入math
包,并允许后续代码引用math
包里的sin
、cos
等函数。但是其实Python函数的返回值仍然是单一值,是一个tuple,但是,在语法上,返回一个tuple可以省略括号,而多个变量可以同时接收一个tuple,按位置赋给对应的值,所以,Python的函数返回多值其实就是返回一个tuple,但写起来更方便。
函数的参数
位置参数
就是直接给的参数,调用函数时,传入的值按照位置顺序依次赋值给位置参数。
默认参数
可以将参数在定义函数时默认值。设置默认参数时需要注意1.一是必选参数在前,默认参数在后 2.当函数有多个参数时,把变化大的参数放前面,变化小的参数放后面。变化小的参数就可以作为默认参数。调用的时候,既可以按顺序提供默认参数,也可以不按顺序提供部分默认参数。当不按顺序提供部分默认参数时,需要把参数名写上。
默认参数有坑:
def add_end(L=[]): |
Python函数在定义的时候,默认参数L
的值就被计算出来了,即[]
,因为默认参数L
也是一个变量,它指向对象[]
,每次调用该函数,如果改变了L
的内容,则下次调用时,默认参数的内容就变了,不再是函数定义时的[]
了。定义默认参数要牢记一点:默认参数必须指向不变对象!
可变参数
定义可变参数和定义一个list或tuple参数相比,仅仅在参数前面加了一个*
号。在函数内部,参数numbers
接收到的是一个tuple,因此,函数代码完全不变。但是,调用该函数时,可以传入任意个参数,包括0个参数:
def calc(*numbers): |
Python允许你在list或tuple前面加一个*
号,把list或tuple的元素变成可变参数传进去:
1, 2, 3] nums = [ |
*nums
表示把nums
这个list的所有元素作为可变参数传进去。这种写法相当有用,而且很常见。
关键字参数
可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。而关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。
def person(name, age, **kw): |
函数person
除了必选参数name
和age
外,还接受关键字参数kw
。在调用该函数时,可以只传入必选参数,也可以传入任意个数的关键字参数:
'Michael', 30) person( |
和可变参数类似,可以将传入**dict:
'city': 'Beijing', 'job': 'Engineer'} extra = { |
**extra
表示把extra
这个dict的所有key-value用关键字参数传入到函数的**kw
参数,kw
将获得一个dict,注意kw
获得的dict是extra
的一份拷贝,对kw
的改动不会影响到函数外的extra
。
命名关键字参数
如果要限制关键字参数的名字,就可以用命名关键字参数,例如,只接收city
和job
作为关键字参数。这种方式定义的函数如下:
def person(name, age, *, city, job): |
和关键字参数**kw
不同,命名关键字参数需要一个特殊分隔符*
,*
后面的参数被视为命名关键字参数。调用方式如下:
'Jack', 24, city='Beijing', job='Engineer') person( |
如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*
了:
def person(name, age, *args, city, job): |
命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错。
命名关键字参数可以有缺省值,从而简化调用,由于命名关键字参数city
具有默认值,调用时,可不传入city
参数:
def person(name, age, *, city='Beijing', job): |
使用命名关键字参数时,要特别注意,如果没有可变参数,就必须加一个*
作为特殊分隔符。如果缺少*
,Python解释器将无法识别位置参数和命名关键字参数
参数组合
在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。例如:
def f1(a, b, c=0, *args, **kw): |
通过一个tuple和dict,你也可以调用上述函数:
1, 2, 3, 4) args = ( |
高级特性
切片
Python提供了切片(Slice)操作符,能大大简化取一个list或tuple的部分元素这种操作
取前3个元素,用一行代码就可以完成切片:
'Michael', 'Sarah', 'Tracy', 'Bob', 'Jack'] L = [ |
L[0:3]
表示,从索引0
开始取,直到索引3
为止,但不包括索引3
。即索引0
,1
,2
,正好是3个元素。
如果第一个索引是0
,还可以省略:
3] L[: |
支持倒数切片:
2:] L[- |
实现前10个数,每两个取一个:
10:2] L[: |
所有数,每5个取一个:
5] L[:: |
甚至什么都不写,只写[:]
就可以原样复制一个list:
L[:] |
tuple也是一种list,唯一区别是tuple不可变。因此,tuple也可以用切片操作,只是操作的结果仍是tuple:
0, 1, 2, 3, 4, 5)[:3] ( |
字符串'xxx'
也可以看成是一种list,每个元素就是一个字符。因此,字符串也可以用切片操作,只是操作结果仍是字符串,Python没有针对字符串的截取函数,只需要切片一个操作就可以完成:
'ABCDEFG'[:3] |
迭代
Python的for
循环不仅可以用在list
或tuple
上,还可以作用在其他可迭代对象上。
list
这种数据类型虽然有下标,但很多其他数据类型是没有下标的,但是,只要是可迭代对象,无论有无下标,都可以迭代,比如dict
就可以迭代:
'a': 1, 'b': 2, 'c': 3} d = { |
因为dict
的存储不是按照list
的方式顺序排列,所以,迭代出的结果顺序很可能不一样。默认情况下,dict
迭代的是key。如果要迭代value,可以用for value in d.values()
,如果要同时迭代key和value,可以用for k, v in d.items()
。
由于字符串也是可迭代对象,因此,也可以作用于for
循环:
for ch in 'ABC': |
Python内置的enumerate
函数可以把一个list
变成索引-元素对,这样就可以在for
循环中同时迭代索引和元素本身:
for i, value in enumerate(['A', 'B', 'C']): |
上面的for
循环里,同时引用了两个变量,在Python里是很常见的,比如下面的代码:
for x, y in [(1, 1), (2, 4), (3, 9)]: |
列表生成式
要生成list [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
可以用list(range(1, 11))
:
list(range(1, 11)) |
但如果要生成[1x1, 2x2, 3x3, ..., 10x10]
怎么做?列表生成式可以用一行语句代替循环生成上面的list:
for x in range(1, 11)] [x * x |
写列表生成式时,把要生成的元素x * x
放到前面,后面跟for
循环,就可以把list创建出来,十分有用。
for循环后面还可以加上if判断,这样我们就可以筛选出仅偶数的平方:
for x in range(1, 11) if x % 2 == 0] [x * x |
还可以使用两层循环,可以生成全排列:
for m in 'ABC' for n in 'XYZ'] [m + n |
for
循环其实可以同时使用两个甚至多个变量,比如dict
的items()
可以同时迭代key和value:
'x': 'A', 'y': 'B', 'z': 'C' } d = { |
因此,列表生成式也可以使用两个变量来生成list:
'x': 'A', 'y': 'B', 'z': 'C' } d = { |
最后把一个list中所有的字符串变成小写:
'Hello', 'World', 'IBM', 'Apple'] L = [ |
以下代码正常输出偶数:
for x in range(1, 11) if x % 2 == 0] [x |
但是,我们不能在最后的if
加上else
,这是因为跟在for
后面的if
是一个筛选条件,不能带else
,否则如何筛选?
把if
写在for
前面必须加else
,否则报错:
if x % 2 == 0 for x in range(1, 11)] [x |
这是因为for
前面的部分是一个表达式,它必须根据x
计算出一个结果。因此,考察表达式:x if x % 2 == 0
,它无法根据x
计算出结果,因为缺少else
,必须加上else
:
if x % 2 == 0 else -x for x in range(1, 11)] [x |
上述for
前面的表达式x if x % 2 == 0 else -x
才能根据x
计算出确定的结果。
可见,在一个列表生成式中,for
前面的if ... else
是表达式,而for
后面的if
是过滤条件,不能带else
。
生成器
一边循环一边计算的机制,称为生成器:generator。这样就不必创建完整的list,从而节省大量的空间。
要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]
改成()
,就创建了一个generator:
for x in range(10)] L = [x * x |
创建L
和g
的区别仅在于最外层的[]
和()
,L
是一个list,而g
是一个generator。
如果要一个一个打印出来,可以通过next()
函数获得generator的下一个返回值。当然,上面这种不断调用next(g)
实在是太变态了,正确的方法是使用for
循环,因为generator也是可迭代对象:
for x in range(10)) g = (x * x |
所以,我们创建了一个generator后,基本上永远不会调用next()
,而是通过for
循环来迭代它
迭代器
可以使用isinstance()
判断一个对象是否是可迭代对象:
from collections.abc import Iterable |
生成器不但可以作用于for
循环,还可以被next()
函数不断调用并返回下一个值。可以被next()
函数调用并不断返回下一个值的对象称为迭代器:Iterator
。
可以使用isinstance()
判断一个对象是否是Iterator
对象:
from collections.abc import Iterator |
把list
、dict
、str
等Iterable
变成Iterator
可以使用iter()
函数:
isinstance(iter([]), Iterator) |
函数式编程
变量可以指向函数,可以把函数本身赋值给变量。一个变量指向了一个函数,可通过该变量来调用这个函数。
abs f = |
变量f
现在已经指向了abs
函数本身。直接调用abs()
函数和调用变量f()
完全相同。
高阶函数
函数名也是变量
函数名其实就是指向函数的变量。对于abs()
这个函数,完全可以把函数名abs
看成变量,它指向一个可以计算绝对值的函数。
如果把abs
指向其他对象,会有什么情况发生?
abs = 10 |
把abs
指向10
后,就无法通过abs(-10)
调用该函数了!因为abs
这个变量已经不指向求绝对值函数而是指向一个整数10
。
传入函数
既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
一个最简单的高阶函数:
def add(x, y, f): |
编写高阶函数,就是让函数的参数能够接收别的函数。
map&reduce
map
map()
函数接收两个参数,一个是函数,一个是Iterable
,map
将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator
返回。
举例说明,比如我们有一个函数f(x)=x2,要把这个函数作用在一个list [1, 2, 3, 4, 5, 6, 7, 8, 9]
上,就可以用map()
实现如下:
def f(x): |
map()
传入的第一个参数是f
,即函数对象本身。由于结果r
是一个Iterator
,Iterator
是惰性序列,因此通过list()
函数让它把整个序列都计算出来并返回一个list。
我们不但可以计算简单的f(x)=x2,还可以计算任意复杂的函数,比如,把这个list所有数字转为字符串:
list(map(str, [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) |
比方说对一个序列求和,就可以用reduce
实现:
from functools import reduce |
from functools import reduce |
这个是map和reduce的运用+函数内定义函数这个用法
filter
和map()
类似,filter()
也接收一个函数和一个序列。和map()
不同的是,filter()
把传入的函数依次作用于每个元素,然后根据返回值是True
还是False
决定保留还是丢弃该元素。
例如,在一个list中,删掉偶数,只保留奇数,可以这么写:
def is_odd(n): |
filter()
函数返回的是一个Iterator
,也就是一个惰性序列,所以要强迫filter()
完成计算结果,需要用list()
函数获得所有结果并返回list。
sorted
Python内置的sorted()
函数就可以对list进行排序:
sorted([36, 5, -12, 9, -21]) |
此外,sorted()
函数也是一个高阶函数,它还可以接收一个key
函数来实现自定义的排序,例如按绝对值大小排序:
sorted([36, 5, -12, 9, -21], key=abs) |
key指定的函数将作用于list的每一个元素上,并根据key函数返回的结果进行排序。对比原始的list和经过key=abs
处理过的list:
list = [36, 5, -12, 9, -21] |
然后sorted()
函数按照keys进行排序,并按照对应关系返回list相应的元素:
keys排序结果 => [5, 9, 12, 21, 36] |
要进行反向排序,不必改动key函数,可以传入第三个参数reverse=True
sorted([36, 5, -12, 9, -21], key=abs,reverse=True) |
返回函数
如果不需要立刻求和,而是在后面的代码中,根据需要再计算。可以不返回求和的结果,而是返回求和的函数:
def lazy_sum(*args): |
当我们调用lazy_sum()
时,返回的并不是求和结果,而是求和函数:
1, 3, 5, 7, 9) f = lazy_sum( |
调用函数f
时,才真正计算求和的结果:
f() |
我们在函数lazy_sum
中又定义了函数sum
,并且,内部函数sum
可以引用外部函数lazy_sum
的参数和局部变量,当lazy_sum
返回函数sum
时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。
当我们调用lazy_sum()
时,每次调用都会返回一个新的函数,即使传入相同的参数:
1, 3, 5, 7, 9) f1 = lazy_sum( |
f1()
和f2()
的调用结果互不影响。
返回闭包时牢记一点:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
匿名函数
在Python中,对匿名函数提供了有限支持。还是以map()
函数为例,计算f(x)=x2时,除了定义一个f(x)
的函数外,还可以直接传入匿名函数:
list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9])) |
通过对比可以看出,匿名函数lambda x: x * x
实际上就是:
def f(x): |
关键字lambda
表示匿名函数,冒号前面的x
表示函数参数。匿名函数有个限制,就是只能有一个表达式,不用写return
,返回值就是该表达式的结果。
匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数:
lambda x: x * x f = |
同样,也可以把匿名函数作为返回值返回,比如:
def build(x, y): |
模块
模块使用
if __name__=='__main__': |
相当于main函数,只有模块内部才能用,其他的模块是没法调用这个的
作用域
正常的函数和变量名是公开的(public),可以被直接引用,比如:abc
,x123
,PI
等;类似__xxx__
这样的变量是特殊变量,可以被直接引用,但是有特殊用途,__name__
就是特殊变量。类似_xxx
和__xxx
这样的函数或变量就是非公开的(private),不应该被直接引用,比如_abc
,__abc
等;
面向对象编程
类和实例
class Student(object): |
(object)
,表示该类是从哪个类继承下来的,如果没有合适的继承类,就使用object
类,这是所有类最终都会继承的类。
可以自由地给一个实例变量绑定属性,比如,给实例bart
绑定一个name
属性:
bart = Student() |
可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的__init__
方法,在创建实例的时候,就把name
,score
等属性绑上去:
class Student(object): |
方法__init__
前后分别有两个下划线。__init__
方法的第一个参数永远是self
,表示创建的实例本身,因此,在__init__
方法内部,就可以把各种属性绑定到self
,因为self
就指向创建的实例本身,相当于this
。有了__init__
方法,在创建实例的时候,就不能传入空的参数了,必须传入与__init__
方法匹配的参数,但self
不需要传,Python解释器自己会把实例变量传进去。
和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self
,并且,调用时,不用传递该参数。要调用一个方法,只需要在实例变量上直接调用,除了self
不用传递,其他参数正常传入。
访问限制
如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__
,在Python中,实例的变量名如果以__
开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问。
需要注意的是,在Python中,变量名类似__xxx__
的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name__
、__score__
这样的变量名。
以一个下划线开头的实例变量名,比如_name
,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。
注意:
'Bart Simpson', 59) bart = Student( |
表面上看,外部代码“成功”地设置了__name
变量,但实际上这个__name
变量和class内部的__name
变量不是一个变量!内部的__name
变量已经被Python解释器自动改成了_Student__name
,而外部代码给bart
新增了一个__name
变量。
继承和多态
判断一个变量是否是某个类型可以用isinstance()
判断:
a = list() # a是list类型 |
在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类。但是,反过来就不行
对于静态语言(例如Java)来说,如果需要传入Animal
类型,则传入的对象必须是Animal
类型或者它的子类,否则,将无法调用run()
方法。
对于Python这样的动态语言来说,则不一定需要传入Animal
类型。我们只需要保证传入的对象有一个run()
方法就可以了:
class Timer(object): |
获取对象信息
type()
type()返回对应的class类型。
基本类型可以用type()来判断:
type(123) |
指向函数或类的变量也用type()判断类型:
type(abs) |
如果在if中判断两个变量type基本类型是否相同,采用如下形式:
type(123)==type(456) |
判断一个对象是否是函数,就要使用types
模块中的常量:
import types |
isinstance()
对于class我们使用isintance()
函数。isinstance()
判断的是一个对象是否是该类型本身,或者位于该类型的父继承链上。总是优先使用isinstance()判断类型,可以将指定类型及其子类“一网打尽”。
如果继承关系是:
object -> Animal -> Dog -> Husky |
那么
isinstance(h, Husky) |
能用type()
判断的基本类型也可以用isinstance()
判断:
isinstance('a', str) |
并且可以判断一个变量是否是某些类型中的一种,例如判断是否是list或者tuple
isinstance([1, 2, 3], (list, tuple)) |
dir()
获取一个对象所有属性和方法,使用dir()
函数,返回一个包含字符串的list,比如获得一个str对象的所有属性和方法:
dir('ABC') |
类似__xxx__
的属性和方法在Python中都是有特殊用途的,比如__len__
方法返回长度。在Python中,如果你调用len()
函数试图获取一个对象的长度,实际上,在len()
函数内部,它自动去调用该对象的__len__()
方法,所以,下面的代码是等价的:
len('ABC') |
仅仅把属性和方法列出来是不够的,配合getattr()
、setattr()
以及hasattr()
,我们可以直接操作一个对象的状态:
class MyObject(object): |
可以测试该对象的属性:
hasattr(obj, 'x') # 有属性'x'吗? |
当获取不存在的属性时会抛出错误:
getattr(obj, 'z') # 获取属性'z' |
可以传入一个default参数,如果属性不存在,就返回默认值:
getattr(obj, 'z', 404) # 获取属性'z',如果不存在,返回默认值404 |
以下是获得对象方法的例子:
hasattr(obj, 'power') # 有属性'power'吗? |
一个例子如下:
def readImage(fp): |
假设我们希望从文件流fp中读取图像,我们首先要判断该fp对象是否存在read方法,如果存在,则该对象是一个流,如果不存在,则无法读取。hasattr()
就派上了用场。
在Python这类动态语言中,根据鸭子类型,有read()
方法,不代表该fp对象就是一个文件流,它也可能是网络流,也可能是内存中的一个字节流,但只要read()
方法返回的是有效的图像数据,就不影响读取图像的功能。
实例属性和类属性
根据类创建的实例可以任意绑定属性,给实例绑定属性的方法是通过实例变量,或者通过self
变量,例如:
class Student(object): |
如果student类本身需要绑定一个属性,可以直接在class中定义属性,这种属性是类属性,归student类所有:
class Student(object): |
当我们定义了一个类属性后,这个属性虽然归类所有,但类的所有实例都可以访问到。
class Student(object): |
不要对实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。
面向对象高级编程
使用__slots__
当我们创建了一个实例后,我们可以给这个实例绑定任何属性和方法。
def set_age(self, age): # 定义一个函数作为实例方法 |
但是,给一个实例绑定的方法,对另一个实例是不起作用的。为了给所有实例都绑定方法,可以给class绑定方法,给class绑定方法后,所有实例均可调用:
def set_score(self, score): |
通常情况下,上面的set_score
方法可以直接定义在class中,但动态绑定允许我们在程序运行的过程中动态给class加上功能。
如果想要限制实例的属性,比如只允许对student实例添加name和age属性,就要在定义class的时候定义一个特殊的__slots__
变量,来限制该class实例能添加的属性:
class Student(object): |
由于'score'
没有被放到__slots__
中,所以不能绑定score
属性,试图绑定score
将得到AttributeError
的错误。使用__slots__
要注意,__slots__
定义的属性仅对当前类实例起作用,对继承的子类是不起作用的:
class GraduateStudent(Student): |
除非在子类中也定义__slots__
,这样,子类实例允许定义的属性就是自身的__slots__
加上父类的__slots__
。