Python 基础
~ 数据类型
(3)除法:
~ 变量
~ 常量
~ 字符编码和编码
- Python的字符串:在最新的Python 3版本中,字符串是以Unicode编码的,也就是说,Python的字符串支持多语言;
- 对于单个字符的编码,Python提供了ord()函数获取字符的整数表示,chr()函数把编码转换为对应的字符:
- ord() 字符 –> 整数
- chr() 整数 –> 字符
>~ ord('A')
65
>~ ord('中')
20013
>~ chr(66)
'B'
>~ chr(25991)
'文'
注意:
由于Python源代码也是一个文本文件,所以,当你的源代码中包含中文的时候,在保存源代码时,就需要务必指定保存为UTF-8编码;当Python解释器读取源代码时,为了让它按UTF-8编码读取,我们通常在文件开头写上这两行:
#!/usr/bin/env python3 //注释是为了告诉Linux/OS X系统,这是一个Python可执行程序,Windows系统会忽略这个注释;
# -*- coding: utf-8 -*- //注释是为了告诉Python解释器,按照UTF-8编码读取源代码,否则,你在源代码中写的中文输出可能会有乱码;
同时必须并且要确保文本编辑器正在使用UTF-8 without BOM编码:
~ 格式化
我们经常会输出类似'亲爱的xxx你好!你xx月的话费是xx,余额是xx'
之类的字符串,而xxx的内容都是根据变量变化的,所以,需要一种简便的格式化字符串的方式;Python中,采用的格式化方式和C语言是一致的,用%实现;
%运算符就是用来格式化字符串的,有几个%?
占位符,后面就跟几个变量或者值,顺序要对应好;如果只有一个%?
,括号可以省略;
- %s表示用字符串替换; 若不确定用什么,就用%s
- %d表示用整数替换;
- %f表示用浮点数替换;
- %x 表示用十六进制整数替换;
>~ 'Hello, %s' % 'world'
'Hello, world'
>~ 'Hi, %s, you have $%d.' % ('Michael', 1000000)
'Hi, Michael, you have $1000000.'
(1)格式化整数和浮点数还可以指定是否补0和整数与小数的位数:
print('%2d-%02d' % (3, 1)) //输出:空3-01
print('%.2f' % 3.1415926) //输出:3.14
(2)有些时候,字符串里面的%是一个普通字符,这个时候就需要转义,用%%来表示一个%:
>~ 'growth rate: %d %%' % 7
'growth rate: 7 %'
~ format()
另一种格式化字符串的方法是使用字符串的format()方法,它会用传入的参数依次替换字符串内的占位符{0}、{1}……,不过这种方式写起来比%要麻烦得多:
>~ 'Hello, {0}, 成绩提升了 {1:.1f}%'.format('小明', 17.125)
'Hello, 小明, 成绩提升了 17.1%'
~ 列表list []
list是一种有序的集合,可以随时添加和删除其中的元素;
>~ classmates = ['Michael', 'Bob', 'Tracy'] //变量classmates就是一个list
>~ classmates
['Michael', 'Bob', 'Tracy']
>~ len(classmates) //用len()函数可以获得list元素的个数
3
>~ classmates[1] //用索引来访问list中每一个位置的元素,记得索引是从0开始的
'Bob'
>~ classmates[-1] //如果要取最后一个元素,除了计算索引位置外,还可以用-1做索引,直接获取最后一个元素
'Tracy'
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ classmates.append('Adam') //list是一个可变的有序表,所以,可以往list中追加元素到末尾;
>~ classmates
['Michael', 'Bob', 'Tracy', 'Adam']
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ classmates.insert(1, 'Jack') //也可以把元素插入到指定的位置,比如索引号为1的位置
>~ classmates
['Michael', 'Jack', 'Bob', 'Tracy', 'Adam']
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ classmates.pop() //要删除list末尾的元素,用pop()方法
'Adam'
>~ classmates
['Michael', 'Jack', 'Bob', 'Tracy']
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ classmates.pop(1) //要删除指定位置的元素,用pop(i)方法,其中i是索引位置:
'Jack'
>~ classmates
['Michael', 'Bob', 'Tracy']
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ classmates[1] = 'Sarah' //要把某个元素替换成别的元素,可以直接赋值给对应的索引位置
>~ classmates
['Michael', 'Sarah', 'Tracy']
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ L = ['Apple', 123, True] //list里面的元素的数据类型也可以不同
>~ s = ['python', 'java', ['asp', 'php'], 'scheme'] //list元素也可以是另一个list ,len(s)=4
>~ L = [] //如果一个list中一个元素也没有,就是一个空的list,它的长度为0,len(L)=0
~ 元组tuple ()
另一种有序列表叫元组:tuple;tuple和list非常类似,但是tuple一旦初始化就不能修改;
>~ classmates = ('Michael', 'Bob', 'Tracy')
classmates这个tuple不能变了,它没有append(),insert()这样的方法;其他获取元素的方法和list是一样的,你可以正常地使用classmates[0],classmates[-1],但不能赋值成另外的元素;
tuple的陷阱:当你定义一个tuple时,在定义的时候,tuple的元素就必须被确定下来,比如:
>~ t = (1, 2)
>~ t
(1, 2)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ t = () //如果要定义一个空的tuple,可以写成():
>~ t
()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ t = (1) //但是,要定义一个只有1个元素的tuple,如果你这么定义,定义的不是tuple,是1这个数!
//这是因为括号()既可以表示tuple,又可以表示数学公式中的小括号,这就产生了歧义,
//因此,Python规定,这种情况下,按小括号进行计算,计算结果自然是1;
>~ t
1
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ t = (1,) //所以,只有1个元素的tuple定义时必须加一个逗号,,来消除歧义;
>~ t
(1,)
~ 条件判断
age = 3
if age >= 18: //注意不要少写了冒号:
print('your age is', age)
print('adult')
elif age >= 6: //elif
print('teenager')
else: //注意不要少写了冒号:
print('kid')
input
input()返回的数据类型是str,str不能直接和整数比较,必须先把str转换成整数;Python提供了int()函数来完成这件事情;
- 若直接将s与2000进行比较,报错;
- 若输入字符串,报错:int()函数发现一个字符串并不是合法的数字时就会报错,程序就退出了;
s = input('birth: ')
birth = int(s)
if birth < 2000:
print('00前')
else:
print('00后')
例题
# -*- coding: utf-8 -*- //告诉Python解释器,按照UTF-8编码读取源代码,否则,源代码中写中文输出可能会有乱码
height = 1.75
weight = 80.5
BIM = (weight/height)**2 //n的m次方:n**m
if BIM<=18.5: //注意写冒号:
print("过轻")
elif 18.5<BIM and BIM<=25: //多个表达式之间用 and 连接
print("正常")
elif 25<BIM and BIM<=28:
print("过重")
elif 28<BIM and BIM<=32:
print("肥胖")
else: //注意写冒号:
print("严重肥胖")
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
输出:
C:\Users\盗情\Desktop>python a.py
严重肥胖
~ 循环
(1)for x in ...
循环:依次把*list或tuple中的每个元素迭代出来*
for x in …循环就是把每个元素代入变量x,然后执行缩进块的语句;
names = ['Michael', 'Bob', 'Tracy']
for name in names:
print(name)
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
range()函数:可以生成一个整数序列;
**例如:**range(101)就可以生成0-100的整数序列;
>~ list(range(5))
[0, 1, 2, 3, 4]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
sum = 0
for x in range(101): //0-100
sum = sum + x
print(sum)
(2)while循环:
sum = 0
n = 99
while n > 0:
sum = sum + n //注意,这行按回车之后,在下一行会自动tab缩进,这行是按空格进行的缩进,两行缩进方式不一样,所以会报错
n = n - 2 //将这行自动Tab缩进 改成按4个空格缩进
print(sum)
break:提前退出循环
continue:结束本次循环,开始下次循环
这两个语句通常都必须配合if语句使用;
~ 字典dict {}
Python内置了字典:dict的支持,dict全称dictionary,在其他语言中也称为map,使用键-值(key-value)存储,具有极快的查找速度;
>~ d = {'Michael': 95, 'Bob': 75, 'Tracy': 85} //初始化时指定键值对
>~ d['Michael']
95
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ d['Adam'] = 67 //把数据放入dict的方法,除了初始化时指定外,还可以通过key放入:
>~ d['Adam']
67
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ d['Jack'] = 90 //由于一个key只能对应一个value,所以,多次对一个key放入value,后面的值会把前面的值冲掉;
>~ d['Jack']
90
>~ d['Jack'] = 88
>~ d['Jack']
88
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ d['Thomas'] //如果key不存在,dict就会报错;
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'Thomas'
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ 'Thomas' in d //要避免key不存在的错误,有两种办法,一是通过in判断key是否存在;
False
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ d.get('Thomas') //二是通过dict提供的get()方法,如果key不存在,可以返回None,或者自己指定的value
>~ d.get('Thomas', -1) //注意:返回None的时候Python的交互环境不显示结果
-1
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ d.pop('Bob') //要删除一个key,用pop(key)方法,对应的value也会从dict中删除
75
>~ d
{'Michael': 95, 'Tracy': 85}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ key = [1, 2, 3] //list不能作为key
>~ d[key] = 'a list'
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
注意:
- 请务必注意,dict内部存放的顺序和key放入的顺序是没有关系的;
- dict的key必须是不可变对象:因为dict根据key来计算value的存储位置,如果每次计算相同的key得出的结果不同,那dict内部就完全混乱了;这个通过key计算位置的算法称为哈希算法(Hash)
- 要保证hash的正确性,作为key的对象就不能变;在Python中,字符串、整数等都是不可变的,因此,可以放心地作为key;而list是可变的,就不能作为key;
~ set {}
set和dict类似,也是一组key的集合,但不存储value;由于key不能重复,所以,在set中,没有重复的key;
要创建一个set,需要提供一个list作为输入集合:
注意:传入的参数[1, 2, 3]是一个list,而显示的{1, 2, 3}只是告诉你这个set内部有1,2,3这3个元素,显示的顺序也不表示set是有序的;
>~ s = set([1, 1, 2, 2, 3, 3])
>~ s
{1, 2, 3} //重复元素在set中自动被过滤
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ s.add(4) //通过add(key)方法可以添加元素到set中,可以重复添加,但不会有效果
>~ s
{1, 2, 3, 4}
>~ s.add(4)
>~ s
{1, 2, 3, 4}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ s.remove(4) //通过remove(key)方法可以删除元素
>~ s
{1, 2, 3}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ s1 = set([1, 2, 3])
>~ s2 = set([2, 3, 4])
>~ s1 & s2 //取交集 &
{2, 3}
>~ s1 | s2 //取并集 |
{1, 2, 3, 4}
set和dict的唯一区别仅在于没有存储对应的value,但是,set的原理和dict一样,所以,同样不可以放入可变对象,因为无法判断两个可变对象是否相等,也就无法保证set内部“不会有重复元素”
~ 不可变对象
str是不变对象,而list是可变对象;
对于不变对象来说,调用对象自身的任意方法,也不会改变该对象自身的内容;相反,这些方法会创建新的对象并返回,这样,就保证了不可变对象本身永远是不可变的;
>~ 每一行都是一个语句,当语句以冒号:结尾时,缩进的语句视为代码块;
Python(3)函数
函数是最基本的一种代码抽象的方式;
~ 调用函数
Python内置了很多有用的函数,我们可以直接调用;官方函数文档:https://docs.python.org/3/library/functions.html
调用函数的时候,如果传入的参数数量不对,会报TypeError的错误,并且Python会明确地告诉你:abs()有且仅有1个参数,但给出了两个 :
TypeError: abs() takes exactly one argument (2 given)
如果传入的参数数量是对的,但参数类型不能被函数所接受,也会报TypeError的错误,并且给出错误信息:str是错误的参数类型:
TypeError: bad operand type for abs(): 'str'
max函数max()可以接收任意多个参数,并返回最大的那个:
>~ max(2, 3, 1, -5) 输出:3
数据类型转换函数
Python内置的常用函数还包括数据类型转换函数,比如int()函数可以把其他数据类型转换为整数;
>~ 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函数,(abs:取绝对值)
>~ a(-1) // 所以也可以通过a调用abs函数
1
~ 定义函数 - def
在Python中,定义一个函数要使用def语句,依次写出函数名、括号、括号中的参数和冒号:,然后,在缩进块中编写函数体,函数的返回值用return语句返回;
def my_abs(x): //注意冒号 :
if x>=0: //注意冒号 :
return x
else: //注意冒号 :
return -x
如果你已经把my_abs()
的函数定义保存为文件名.py
文件了,那么,可以在该文件的当前目录下启动Python解释器,用from 文件名 import my_abs
来导入my_abs()
函数;
>~ from a import my_abs
>~ my_abs(-89)
89
- 直接运行.py文件时,在cmd下输入
python a.py
- 使用自定义函数时,在交互环境中先引入自定义函数
from a import my_abs
,再调用自定义函数my_abs(-89)
~ 空函数 - pass语句
如果想定义一个什么事也不做的空函数,可以用pass语句:
def nop():
pass
if age >= 18: //pass还可以用在其他语句里
pass //缺少了pass,代码运行就会有语法错误
pass语句什么都不做,可以用来作为占位符,比如现在还没想好怎么写函数的代码,就可以先放一个pass,让代码能运行起来;
~ 参数检查isinstance
- 调用函数时,如果参数个数不对,Python解释器会自动检查出来,并抛出TypeError;(与Python内置函数一致);
- 但是如果参数类型不对,Python解释器就无法帮我们检查;(与Python内置函数不一样)
当传入了不恰当的参数时,内置函数abs会检查出参数错误,而我们定义的my_abs没有参数检查,会导致if语句出错,出错信息和abs不一样;所以,这个函数定义不够完善;
完善函数 需要修改一下my_abs的定义:对参数类型做检查,只允许整数和浮点数类型的参数;数据类型检查可以用内置函数isinstance()实现:
def my_abs(x):
if not isinstance(x, (int, float)): //如果x不是int、float则报错
raise TypeError('bad operand type') //报错显示的信息
if x >= 0:
return x
else:
return -x
~ 函数返回多个值 - 返回的是一个值tuple
比如在游戏中经常需要从一个点移动到另一个点,给出坐标、位移和角度,就可以计算出新的新的坐标:
import math //import math语句表示导入math包,并允许后续代码引用math包里的sin、cos等函数;
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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ r = move(100, 100, 60, math.pi / 6) //但其实这只是一种假象,Python函数返回的仍然是单一值
>~ print(r)
(151.96152422706632, 70.0)
原来返回值是一个tuple!但是,在语法上,返回一个tuple可以省略括号,而多个变量可以同时接收一个tuple,按位置赋给对应的值,所以,Python的函数返回多值其实就是返回一个tuple,但写起来更方便;
~ 函数的参数
Python的函数定义非常简单,但灵活度却非常大;除了正常定义的必选参数外,还可以使用默认参数、可变参数和关键字参数,使得函数定义出来的接口,不但能处理复杂的参数,还可以简化调用者的代码
(1)位置参数
power(x)函数,计算x的平方,参数x就是一个位置参数;
修改后的power(x, n)函数有两个参数:x和n,这两个参数都是位置参数,调用函数时,传入的两个值按照位置顺序依次赋给参数x和n;
(2)默认参数 - n=value
新的power(x, n)函数定义没有问题,但是,旧的调用代码失败了,原因是我们增加了一个参数,导致旧的代码因为缺少一个参数而无法正常调用:
>~ power(5)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: power() missing 1 required positional argument: 'n' //调用函数power()缺少了一个位置参数n
这个时候,默认参数就排上用场了:
def power(x, n=2): //由于我们经常计算x2,所以,完全可以把第二个参数n的默认值设定为2
s = 1
while n > 0:
n = n - 1
s = s * x
return s
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ power(5) //当我们调用power(5)时,相当于调用power(5, 2):
25
>~ power(5, 2)
25
- 默认参数可以简化函数的调用;设置默认参数时,有几点要注意:
- 一是必选参数在前,默认参数在后,否则Python的解释器会报错;
- 二是如何设置默认参数;
- 当函数有多个参数时,把变化大的参数放前面,变化小的参数放后面;变化小的参数就可以作为默认参数;
- 定义默认参数要牢记一点:默认参数必须指向不变对象;
(3)可变参数 - 参数前面加星号*
在Python函数中,还可以定义可变参数;顾名思义,可变参数就是传入的参数个数是可变的,可以是1个、2个到任意个,还可以是0个;
给定一组数字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
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ calc([1, 2, 3]) //调用的时候,需要先组装出一个list或tuple
14
>~ calc((1, 3, 5, 7))
84
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def calc(*numbers): //定义可变参数,在参数前面加 *
sum = 0
for n in numbers:
sum = sum + n * n
return sum
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ calc(1, 3, 5, 7) //调用该函数时,可以传入任意个参数,包括0个参数;
84
>~ calc()
0
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ nums = [1, 2, 3] //如果已经有一个list或者tuple,要调用一个可变参数,在list或tuple前面加一个*号,把list或tuple的元素变成可变参数传进去;
>~ calc(*nums)
14
(4)关键字参数 - 参数前面加两个星号**
- 可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple;
- 关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict;
def person(name, age, **kw): //函数person除了必选参数name和age外,还接受关键字参数kw
print('name:', name, 'age:', age, 'other:', 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': 'Engineer'} //和可变参数类似,也可以先组装出一个dict,然后,把该dict转换为关键字参数传进去
>~ 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;
(5)命名关键字参数
- 对于关键字参数,函数的调用者可以传入任意不受限制的关键字参数;
- 对于命名关键字参数,可以限制关键字参数的名字,只接受设置的关键字参数;
def person(name, age, *, city, job): //命名关键字参数需要一个特殊分隔符 * , * 后面的参数被视为命名关键字参数;
print(name, age, city, job)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ person('Jack', 24, city='Beijing', job='Engineer')
Jack 24 Beijing Engineer
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def person(name, age, *args, city, job): //如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了
print(name, age, args, city, job)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ person('Jack', 24, 'Beijing', 'Engineer') //命名关键字参数必须传入参数名,这和位置参数不同;如果没有传入参数名,调用将报错
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: person() takes 2 positional arguments but 4 were given
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def person(name, age, *, city='Beijing', job): //命名关键字参数可以有缺省值,从而简化调用:定义city默认值Beijing
print(name, age, city, job)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ person('Jack', 24, job='Engineer') //由于命名关键字参数city具有默认值,调用时,可不传入city参数:
Jack 24 Beijing Engineer
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def person(name, age, city, job): //使用命名关键字参数时,要特别注意,若没有可变参数,就必须加一个*作为特殊分隔符;
pass //如果缺少*,Python解释器将无法识别位置参数和命名关键字参数:city和job被视为位置参数
(6)参数组合
- 在Python中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用;
- 参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数;
- 在函数调用的时候,Python解释器自动按照参数位置和参数名把对应的参数传进去;
- 对于任意函数,都可以通过类似func(*args, **kw)的形式调用它,无论它的参数是如何定义的;
def f1(a, b, c=0, *args, **kw): //ab是必选参数(位置参数),c是默认参数,args是可变参数,kw是关键字参数
print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)
def f2(a, b, c=0, *, d, **kw): //d是命名关键字参数,调用时需要传入参数名:key=value ;
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}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ args = (1, 2, 3, 4) //通过一个tuple和dict,你也可以调用上述函数
>~ 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': '#'}
练习:以下函数允许计算两个数的乘积,请稍加改造,变成可接收一个或多个数并计算乘积;
def product(x, y):
return x * y
def product(x,*args):
sum = 1
for n in args :
sum = sum * n
return sum*x
(7)小结
- 默认参数一定要用不可变对象,如果是可变对象,程序运行时会有逻辑错误!
- 要注意定义可变参数和关键字参数的语法:
*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})
;
- 可变参数既可以直接传入:func(1, 2, 3),又可以先组装list或tuple,再通过
- 使用
*args
和**kw
是Python的习惯写法,当然也可以用其他参数名,但最好使用习惯用法; - 命名的关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值;
- 定义命名的关键字参数在没有可变参数的情况下不要忘了写分隔符
*
,否则定义的将是位置参数;
~ 递归函数
在函数内部,可以调用其他函数;如果一个函数在内部调用自身本身,这个函数就是递归函数;
- 注意:递归调用的次数过多,会导致栈溢出!
def fact(n):
if n==1:
return 1
return n * fact(n - 1)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~ fact(1000) //栈溢出
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in fact
...
File "<stdin>", line 4, in fact
RuntimeError: maximum recursion depth exceeded in comparison
解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的;
尾递归
是指在函数返回的时候,调用自身本身,并且,return语句不能包含表达式;这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况;
上面的fact(n)
函数由于return n * fact(n - 1)
引入了乘法表达式,所以就不是尾递归了;要改成尾递归方式,需要多一点代码,主要是要把每一步的乘积传入到递归函数中:
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)
Python(4)高级特性
~ 切片Slice
切片:用于取一个list、tuple、字符串的部分元素;相当于针对字符串提供的很多各种截取函数(例如,substring),其实目的就是对字符串切片;
>~ L = list(range(100)) //创建一个0-99的数列
>~ L
[0, 1, 2, 3, ..., 99]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ L[0:10] //取出前10个数,从索引0开始取,直到索引3为止,包含左,不包含右;
>~ L[:10] //如果第一个索引是0,还可以省略
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ L[10:20] //也可以从索引10开始,取出10个元素出来
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ L[-10:] //取出后10个数;(Python支持L[-1]取倒数第一个元素,那么它同样支持倒数切片)
[90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ L[:10:2] //前10个数,每两个取一个
[0, 2, 4, 6, 8]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ L[::5] //所有数,每5个取一个
[0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ L[:] //什么都不写,只写[:]就可以原样复制一个list
[0, 1, 2, 3, ..., 99]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ (0, 1, 2, 3, 4, 5)[:3] //tuple也是一种list,唯一区别是tuple不可变;因此,tuple也可以用切片操作,只是操作的结果仍是tuple
(0, 1, 2)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ 'ABCDEFG'[:3] //字符串'xxx'也可以看成是一种list,每个元素就是一个字符;因此,字符串也可以用切片操作,只是操作结果仍是字符串
'ABC'
>~ 'ABCDEFG'[::2]
'ACEG'
~ 迭代Iteration
- 迭代(Iteration):通过
for ... in
循环来遍历一个给定list、tuple、其他可迭代对象; - 只要是可迭代对象,无论有无下标,都可以迭代,比如dict就可以迭代;
- 迭代key :
for key in d:
- 迭代key :
for value in d.values():
- 同时迭代key和value :
for k, v in d.items():
- 迭代字符串 :
for ch in 'ABC':
- 迭代key :
>~ d = {'a': 1, 'b': 2, 'c': 3} //迭代dict
>~ for key in d: //默认情况下,dict迭代的是key
... print(key)
...
a //因为dict的存储不是按照list的方式顺序排列,所以,迭代出的结果顺序很可能不一样
c
b
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ for value in d.values(): //迭代value
>~ for k, v in d.items(): //同时迭代key和value
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ for ch in 'ABC': //迭代字符串
... print(ch)
...
A
B
C
通过collections模块的Iterable类型判断一个对象是可迭代对象
>~ from collections import Iterable //引入Iterable
>~ isinstance('abc', Iterable) //str是否可迭代
True
>~ isinstance([1,2,3], Iterable) //list是否可迭代
True
>~ isinstance(123, Iterable) //整数是否可迭代
False
对list实现类似Java那样的下标循环
Python内置的enumerate函数可以把一个list变成索引-元素对,这样就可以在for循环中同时迭代索引和元素本身:
>~ for i, value in enumerate(['A', 'B', 'C']):
... print(i, value)
...
0 A
1 B
2 C
同时引用了两个变量
>~ for x, y in [(1, 1), (2, 4), (3, 9)]:
... print(x, y)
...
1 1
2 4
3 9
练习:请使用迭代查找一个list中最小和最大值,并返回一个tuple
def q(L):
if L==[]:
return (None, None)
else:
m=L[0]
n=L[0]
for x in L:
if m>x:
m=x
if n<x:
n=x
return (m,n)
~ 列表生成式List Comprehensions
列表生成式是Python内置的非常简单却强大的可以用来创建list的生成式;通过列表生成式,我们可以直接创建一个列表;但是,受到内存限制,列表容量肯定是有限的;
生成[1x1, 2x2, 3x3, ..., 10x10]
,用循环太繁琐,而列表生成式则可以用一行语句代替循环生成上面的list;
>~ [x * x for x in range(1, 11)] //写列表生成式时,把要生成的元素x * x放到前面,后面跟for循环
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ [x * x for x in range(1, 11) if x % 2 == 0] //for循环后面还可以加上if判断,筛选出仅偶数的平方
[4, 16, 36, 64, 100]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ [m + n for m in 'ABC' for n in 'XYZ'] //使用两层循环,可以生成全排列
['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ d = {'x': 'A', 'y': 'B', 'z': 'C' } //列表生成式也可以使用两个变量来生成list
>~ [k + '=' + v for k, v in d.items()]
['y=B', 'x=A', 'z=C']
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ L = ['Hello', 'World', 'IBM', 'Apple'] //把一个list中所有的字符串变成小写
>~ [s.lower() for s in L] //如果list中既包含字符串,又包含整数,由于非字符串类型没有lower()方法,所以列表生成式会报错
['hello', 'world', 'ibm', 'apple']
~ 生成器generator
通过列表生成式,我们可以直接创建一个列表;但是,受到内存限制,列表容量肯定是有限的;而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了;
在Python中,一边循环一边计算的机制,称为生成器:generator;生成器在循环的过程中不断推算出后续的元素,这样就不必创建完整的list,从而节省大量的空间;
创建一个generator方法一:
把一个列表生成式的[]
改成()
,就创建了一个generator;
>~ L = [x * x for x in range(10)] //创建了一个列表list
>~ L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>~ g = (x * x for x in range(10)) //创建了一个生成器generator
>~ g
<generator object <genexpr> at 0x1022ef630>
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ next(g) //要一个一个打印出generator的每一个元素,可以通过next()函数获得generator的下一个返回值
0
.......
>~ next(g) //generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ g = (x * x for x in range(10)) //创建了一个generator后,通过for循环来迭代它,不需关心StopIteration的错误
>~ for n in g:
... print(n)
创建一个generator方法二:
如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现;
比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:1, 1, 2, 3, 5, 8, 13, 21, 34, ...
; 斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易;
函数和generator仅一步之遥;要把fib函数变成generator,只需要把print(b)
改为yield b
就可以了:
def fib(max):
n, a, b = 0, 0, 1
while n < max:
//print(b)
yield b //如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator;
a, b = b, a + b
n = n + 1
return 'done'
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ f = fib(6) //f是一个generator
>~ f
<generator object fib at 0x104feaaa0>
>>>next(f)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ for n in fib(6): //使用for循环来迭代generator的值
... print(n)
...
1
1
2
3
5
8
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//但是用for循环调用generator时,发现拿不到generator的return语句的返回值;如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中
>~ g = fib(6)
>~ while True:
... try:
... x = next(g)
... print('g:', x)
... except StopIteration as e:
... print('Generator return value:', e.value)
... break
...
g: 1
g: 1
g: 2
g: 3
g: 5
g: 8
Generator return value: done
最难理解的就是generator和函数的执行流程不一样:
- 函数是顺序执行,遇到return语句或者最后一行函数语句就返回;
- 而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行;
~ 迭代器 Iterator(可迭代对象 Iterable)
可以直接作用于for循环的数据类型有以下几种:
- 一类是集合数据类型,如list、tuple、dict、set、str等;
一类是generator,包括生成器和带yield的generator function;
生成器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了;Iterable:这些可以直接作用于for循环的对象统称为可迭代对象;可以使用isinstance()判断一个对象是否是Iterable对象;
- Iterator:可以被next()函数调用并不断返回下一个值的对象称为迭代器;可以使用isinstance()判断一个对象是否是Iterator对象
iter()函数**
生成器都是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的for循环本质上就是通过不断调用next()函数实现的;
Python(5)函数式编程 Functional Programming
~ 函数式编程
函数与函数式编程
- 函数是面向过程的程序设计的基本单元;
- 函数式编程(Functional Programming),虽然也可以归结到面向过程的程序设计,但其思想更接近数学计算;
对于编程语言:
- 越低级的编程语言,越贴近计算机,抽象程度低,执行效率高,比如C语言;
- 越高级的编程语言,越贴近计算,抽象程度高,执行效率低,比如Lisp语言;
函数式编程就是一种抽象程度很高的编程范式:
- 纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用;
- 允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的;
Python对函数式编程提供部分支持;由于Python允许使用变量,因此,Python不是纯函数式编程语言;
(1)什么是函数式编程
函数式编程使用一系列的函数解决问题;
- 函数仅接受输入并产生输出,不包含 任何能影响产生输出的 内部状态,输出只依赖于输入;
- 任何情况下,使用相同的参数调用函数始终能产生同样的结果;
- 函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数;
- 在一个函数式的程序中,输入的数据“流过”一系列的函数,每一个函数根据它的输入产生输出;
- 函数式风格避免编写有“边界效应”的函数:修改内部状态,或者是其他无法反应在输出上的变化;
- 完全没有边界效应的函数被称为“纯函数式的”;避免边界效应意味着不使用在程序运行时可变的数据结构;
可以认为函数式编程刚好站在了面向对象编程的对立面,但是同时使用函数式编程和面向对象编程:
- 对象通常包含内部状态(字段),和许多能修改这些状态的函数,程序则由不断修改状态构成;
- 函数式编程则极力避免状态改动,并通过在函数间传递数据流进行工作;
(2)为什么使用函数式编程 (优点)
- 逻辑可证:没有边界效应使得更容易从逻辑上证明程序是正确的(而不是通过测试);
- 模块化:函数式编程推崇简单原则,一个函数只做一件事情,将大的功能拆分成尽可能小的模块;小的函数更易于阅读和检查错误;
- 组件化:小的函数更容易加以组合形成新的功能;
- 易于调试:细化的、定义清晰的函数使得调试更加简单;当程序不正常运行时,每一个函数都是检查数据是否正确的接口,能更快速地排除没有问题的代码,定位到出现问题的地方;
- 易于测试:不依赖于系统状态的函数无须在测试前构造测试桩,使得编写单元测试更加容易;
- 更高的生产率:函数式编程产生的代码比其他技术更少(往往是其他技术的一半左右),并且更容易阅读和维护;
~ 高阶函数
既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数; 编写高阶函数,就是让函数的参数能够接收别的函数;
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回;
(1)变量可以指向函数
>~ abs(-10) //abs(-10)是函数调用
10
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ abs //abs是函数本身
<built-in function abs>
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ x = abs(-10) //把函数调用结果赋值给变量
>~ x
10
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ f = abs //把函数本身赋值给变量,即:变量可以指向函数
>~ f
<built-in function abs> //变量f现在已经指向了abs函数本身;直接调用abs()函数和调用变量f()完全相同
>~ f(-10)
10
(2)函数名也是变量
函数名就是指向函数的变量;对于abs()这个函数,完全可以把函数名abs看成变量,它指向一个可以计算绝对值的函数;
如果把abs指向其他对象:
>~ abs = 10
>~ abs(-10)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable
把abs指向10后,就无法通过abs(-10)调用该函数了!因为abs这个变量已经不指向求绝对值函数而是指向一个整数10;
当然实际代码绝对不能这么写,这里是为了说明函数名也是变量;要恢复abs函数,请重启Python交互环境;
- 注:由于abs函数实际上是定义在
import builtins
模块中的,所以要让修改abs变量的指向在其它模块也生效,要用import builtins; builtins.abs = 10
;
(3)传入函数
Eg:
def add(x, y, f): //调用add(-5, 6, abs)时,参数x,y和f分别接收-5,6和abs
return f(x) + f(y) //结果返回11
(4)高阶函数map/reduce函数
- map()函数接收两个参数:一个是函数f(x),一个是Iterable;
map将传入的函数f依次作用到序列的每个元素,并把结果作为新的Iterator返回;
>~ def f(x): //函数f(x)=x^2
... return x * x
...
>~ r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9]) //map()传入的第一个参数是f,即函数对象本身;
>~ list(r) //结果r是一个Iterator,Iterator是惰性序列,通过list()函数让它把整个序列都计算出来并返回一个list
[1, 4, 9, 16, 25, 36, 49, 64, 81]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ list(map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9])) //把list所有数字转为字符串
['1', '2', '3', '4', '5', '6', '7', '8', '9']
- reduce()函数接收两个参数:一个是函数f(x,y),一个是Iterable;
把一个函数f作用在一个序列[x1, x2, x3, …]上,这个函数必须接收两个参数x,y,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
>~ from functools import reduce //Eg1:把序列[1, 3, 5, 7, 9]变换成int:13579
>~ def fn(x, y):
... return x * 10 + y
...
>~ reduce(fn, [1, 3, 5, 7, 9])
13579
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ from functools import reduce //Eg2:把字符串str:‘13579’ 变换成 int:13579
>~ def fn(x, y):
... return x * 10 + y
...
>~ def char2num(s):
... digits = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
... return digits[s]
...
>~ reduce(fn, map(char2num, '13579')) //先用map调用函数char2num 将字符串‘13579’ 变换成序列[1, 3, 5, 7, 9],再reduce 变换成int
13579
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//将交互模式下的输入步骤 整理成一个str2int的函数:
from functools import reduce //引入reduce
DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9} //定义常量list
def str2int(s): //自定义函数str2int(s)
def fn(x, y): //自定义函数里面再自定义一个函数fn
return x * 10 + y
def char2num(s): //自定义函数里面再自定义一个函数char2num
return DIGITS[s]
return reduce(fn, map(char2num, s)) //使用reduce返回结果13579
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
from functools import reduce //用lambda函数进一步简化函数
DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
def char2num(s):
return DIGITS[s]
def str2int(s):
return reduce(lambda x, y: x * 10 + y, map(char2num, s))
(5)filter筛选
filter()函数用于过滤(筛选)序列;接收两个参数:一个是函数,一个是序列;
- 和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素;
- filter()函数返回的是一个Iterator,也就是一个惰性序列,所以要强迫filter()完成计算结果,需要用list()函数获得所有结果并返回list;
练习:用filter求素数:(>1的整数 没有因数)
def _odd_iter(): //先构造一个从3开始的奇数序列(偶数都不是素数)
n = 1 //注意这是一个生成器,并且是一个无限序列
while True:
n = n + 2
yield n
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def _not_divisible(n): //定义一个筛选函数,也是filter函数的第一个参数
return lambda x: x % n > 0 //返回
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def primes(): //定义一个生成器,不断返回下一个素数
yield 2 //这个生成器先返回第一个素数2,然后,利用filter()不断产生筛选后的新的序列
it = _odd_iter() //初始序列
while True:
n = next(it) //返回序列的第一个数
yield n
it = filter(_not_divisible(n), it) //构造新序列
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
for n in primes(): //打印1000以内的素数:
if n < 1000:
print(n)
else:
break //由于primes()也是一个无限序列,所以调用时需要设置一个退出循环的条件
(6)sorted 排序算法
Python内置的sorted()函数就可以对list进行排序;
- sorted()函数也是一个高阶函数;
- 用sorted()排序的关键在于实现一个映射函数;
- 它还可以接收一个
**key=函数**
参数来实现自定义的排序 ;key指定的函数将作用于list的每一个元素上,并根据key函数返回的结果(新的list列表keys)进行排序;sorted()函数按照keys进行排序之后,再按照对应关系返回list相应的元素;
>~ sorted([36, 5, -12, 9, -21]) //传入list 按大小排序
[-21, -12, 5, 9, 36]
>~ sorted([36, 5, -12, 9, -21], key=abs) //传入两个参数:list、key=函数,按绝对值大小排序
[5, 9, -12, -21, 36]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ sorted(['bob', 'about', 'Zoo', 'Credit']) //字符串排序:是按照ASCII的大小比较的,
['Credit', 'Zoo', 'about', 'bob'] //由于'Z' < 'a',结果,大写字母Z会排在小写字母a的前面
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower) //字符串排序,忽略大小写;给sorted传入key函数,
['about', 'bob', 'Credit', 'Zoo'] //用一个key函数把字符串映射为忽略大小写排序即可
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)] //用一组tuple表示学生名字和成绩
def by_name(t): //按名字排序
return t[0].lower()
def by_score(t): //按成绩从高到低排序
return t[1]
L2 = sorted(L, key=by_name)
print(L2)
L2 = sorted(L, key=by_score)
print(L2)
~ 返回函数 - 把函数作为返回值
- 高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回;
def calc_sum(*args): //可变参数求和
ax = 0
for n in args:
ax = ax + n
return ax
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def lazy_sum(*args): //不需要立刻求和,而是在后面的代码中,根据需要再计算
def sum(): //可以不返回求和的结果,而是返回求和的函数
ax = 0
for n in args:
ax = ax + n
return ax
return sum
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ f = lazy_sum(1, 3, 5, 7, 9) //调用lazy_sum()时,返回的并不是求和结果,而是求和函数
>~ f //相当于f=sum
<function lazy_sum.<locals>.sum at 0x101c6ed90>
>~ f() //调用函数f时,才真正计算求和的结果
25
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ f1 = lazy_sum(1, 3, 5, 7, 9)
>~ f2 = lazy_sum(1, 3, 5, 7, 9)
>~ f1==f2 //注意:当我们调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数
False //f1()和f2()的调用结果互不影响
闭包Closure
在上面例子中,我们在函数lazy_sum中又定义了函数sum,并且,内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”;
注意:
- 返回的函数并没有立刻执行,而是直到调用了f()才执行;
- 返回闭包时:返回函数不要引用任何循环变量,或者后续会发生变化的变量;
如果一定要引用循环变量怎么办?
方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变;
def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def count():
def f(j):
def g():
return j*j
return g
fs = []
for i in range(1, 4):
fs.append(f(i)) # f(i)立刻被执行,因此i的当前值被传入f()
return fs
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ f1, f2, f3 = count()
>~ f1()
1
>~ f2()
4
>~ f3()
9
修改前:全部都是9!原因就在于返回的函数引用了变量i,但它并非立刻执行;等到3个函数都返回时,它们所引用的变量i已经变成了3,因此最终结果为9;
~ 匿名函数lambda
匿名函数lambda x: x * x
- 冒号前面的x表示函数参数;
- 匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果;
- 匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数;
f = lambda x: x * x
- 可以把匿名函数作为返回值返回;
return lambda: x * x + y * y
匿名函数相当于:
def f(x):
return x * x
~ 装饰器Decorator
装饰器Decorator:在代码运行期间动态增加功能;Eg:在函数调用前后自动打印日志,但又不希望修改now()函数的定义
- 本质上,decorator就是一个返回函数的高阶函数;
- 函数对象有一个name属性,可以拿到函数的名字;
函数名.__name__
(1)不传入参数:两层嵌套
import functools
def log(func): //定义一个能打印日志的decorator
@functools.wraps(func) //wrapper.__name__ = func.__name__把原始函数的__name__等属性复制到wrapper()函数中
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw) //调用原始函数
return wrapper
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@log (now = log(now)) //借助Python的@语法,把decorator置于函数的定义处
def now():
print('2015-3-25')
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ now() //调用now()函数,不仅会运行now()函数本身,还会在运行now()函数前打印一行日志
call now(): //now()指向了wrapper()函数
2015-3-25
由于log()
是一个decorator,返回一个函数,所以,原来的now()
函数仍然存在,只是现在同名的now变量指向了新的函数,于是调用now()
将执行新函数,即在log()
函数中返回的wrapper()
函数;
wrapper()
函数的参数定义是(*args, **kw)
,因此,wrapper()
函数可以接受任意参数的调用;在wrapper()
函数内,首先打印日志,再紧接着调用原始函数;
(2)传入参数:三层嵌套
import functools
def log(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@log('execute') ( now = log('execute')(now))
def now():
print('2015-3-25')
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ now()
execute now():
2015-3-25
首先执行log('execute')
,返回的是decorator函数,再调用返回的函数,参数是now函数,返回值最终是wrapper函数;
以上两种decorator的定义都没有问题,但还差最后一步;因为我们讲了函数也是对象,它有name等属性,但你去看经过decorator装饰之后的函数,它们的name已经从原来的’now’变成了’wrapper’;
~ 偏函数Partial function
>~ int('12345') //int()函数可以把字符串转换为整数,当仅传入字符串时,int()函数默认按十进制转换
12345
>~ int('12345', base=8) //int()函数还提供额外的base参数,默认值为10;如果传入base参数,就可以做N进制的转换
5349
>~ int('12345', 16)
74565
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def int2(x, base=2): //定义一个int2()的函数,默认把base=2传进去,固定base值
return int(x, base)
>~ int2('1000000') //调用方便了
64
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ import functools
>~ int2 = functools.partial(int, base=2) //functools.partial用来创建一个偏函数,不需要自己定义int2()
>~ int2('1000000')
64
>~ int2('1000000', base=10) //把base参数重新设定默认值为2,也可以在函数调用时传入其他值
1000000
functools.partial
的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单;
创建偏函数时,实际上可以接收函数对象、*args
和**kw
这3个参数;
max2 = functools.partial(max, 10) //把10作为*args的一部分自动加到左边
max2(5, 6, 7)
相当于:
args = (10, 5, 6, 7)
max(*args)
Python(6)模块Module
一个
.py
文件就称之为一个模块(Module),相当于java的类;模块是一组Python代码的集合,可以使用其他模块,也可以被其他模块使用;
模块存放在目录(包Package)里;
每一个包目录下面都会有一个
__init__.py
的文件,这个文件是必须存在的,否则,Python就把这个目录当成普通目录,而不是一个包;__init__.py
可以是空文件,也可以有Python代码,因为__init__.py
本身就是一个模块,而它的模块名就是mycompany;可以有多级目录,组成多级层次的包结构;
文件www.py
的模块名就是mycompany.web.www
,两个文件utils.py
的模块名分别是mycompany.utils
和mycompany.web.utils
~ 使用模块
hello.py
#!/usr/bin/env python3 //标准注释:可以让这个hello.py文件直接在Unix/Linux/Mac上运行
# -*- coding: utf-8 -*- //标准注释:表示.py文件本身使用标准UTF-8编码
' a test module ' //是一个字符串,表示模块的文档注释,任何模块代码的第一个字符串都被视为模块的文档注释;
__author__ = 'Michael Liao' //使用__author__变量,把作者写进去
import sys //导入sys模块,有了变量sys指向该模块,利用这个变量,可访问sys模块的所有功能
def test():
args = sys.argv //sys模块有一个argv变量,用list存储了命令行的所有参数;argv至少有一个元素,因为第一个参数永远是该.py文件的名称
//运行python3 hello.py获得的sys.argv就是['hello.py'];
//运行python3 hello.py Michael获得的sys.argv就是['hello.py', 'Michael]
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()
//当我们在命令行运行hello模块文件时,Python解释器把一个特殊变量__name__置为__main__,而如果在其他地方导入该hello模块时,if判断将失败,因此,这种if测试可以让一个模块通过命令行运行时执行一些额外的代码,最常见的就是运行测试
~ 作用域
在Python中,是通过
_
前缀来控制函数、变量的作用域;正常的函数和变量名是公开的(public),可以被直接引用,比如:abc,x123,PI等;
类似
__xxx__
这样的变量是特殊变量,可以被直接引用,但是有特殊用途,比如上面的__author__
,__name__
就是特殊变量,hello模块定义的文档注释也可以用特殊变量__doc__
访问,我们自己的变量一般不要用这种变量名;类似
_xxx
和__xxx
这样的函数或变量就是非公开的(private),不应该被直接引用,比如_abc
,__abc
等;之所以说,private函数和变量“不应该”被直接引用,而不是“不能”被直接引用,是因为Python并没有一种方法可以完全限制访问private函数或变量,但是,从编程习惯上不应该引用private函数或变量;
外部不需要引用的函数全部定义成private,只有外部需要引用的函数才定义为public;
Python(7)面向对象编程
在Python中,所有数据类型都可以视为对象,当然也可以自定义对象;自定义的对象数据类型就是面向对象中的类(Class)的概念
面向对象的设计思想是抽象出Class,根据Class创建Instance;
面向对象的抽象程度又比函数要高,因为一个Class既包含数据,又包含操作数据的方法;
类是抽象的模板,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同;
在Python中,定义类是通过class关键字;
~ 类和实例
- 处理学生的成绩表:
- 采用面向对象的程序设计思想,我们首选思考的不是程序的执行流程,而是Student这种数据类型应该被视为一个对象,这个对象拥有name和score这两个属性(Property);如果要打印一个学生的成绩,首先必须创建出这个学生对应的对象,然后,给对象发一个print_score消息,让对象自己把自己的数据打印出来;
- 给对象发消息实际上就是调用对象对应的关联函数,我们称之为对象的方法(Method);
class Student(object): //class后面紧接着是类名,即Student,类名通常是大写开头的单词,紧接着是(object),
//表示该类是从哪个类继承下来的,若没有合适的继承类,就使用object类,这是所有类最终都会继承的类;
def __init__(self, name, score): //通过定义__init__方法,在创建实例时,就把name,score等属性强制绑上去
self.__name = name //__init__方法的第一个参数永远是self,表示创建的实例本身
self.__core = score //有了__init__方法,创建实例时,就不能传入空的参数了,必须传入与__init__方法匹配的参数,但self不需要传
def print_score(self):
print('%s: %s' % (self.__name, self.__score))
def get_name(self):
return self.__name
def set_score(self, score):
self.__score = score
bart = Student('Bart Simpson', 59) //定义好Student类,就可根据Student类创建出Student的实例,创建实例:类名+()
lisa = Student('Lisa Simpson', 87)
bart.print_score()
lisa.print_score()
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>~ bart = Student() //变量bart指向的就是一个Student的实例,后面的0x10a67a590是内存地址,每个object的地址都不一样
>~ bart
<__main__.Student object at 0x10a67a590>
>~ Student //Student本身则是一个类
<class '__main__.Student'>
>~ bart.name = 'Bart Simpson' //可以自由地给一个实例变量绑定属性,比如,给实例bart绑定一个name属性
>~ bart.name
'Bart Simpson'
和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且,调用时,不用传递该参数;除此之外,类的方法和普通函数没有什么区别,所以,你仍然可以用默认参数、可变参数、关键字参数和命名关键字参数;
~ 数据封装
类的方法:封装数据的函数;这些方法是和类本身关联起来的;和普通函数不同,方法可以直接访问实例的数据;
要定义一个方法,除了第一个参数是self外,其他和普通函数一样;要调用一个方法,只需要在实例变量上直接调用,除了self不用传递,其他参数正常传入;
通过在实例上调用方法,我们就直接操作了对象内部的数据,但无需知道方法内部的实现细节;
和静态语言不同,Python允许对实例变量绑定任何数据,也就是说,对于两个实例变量,虽然它们都是同一个类的不同实例,但拥有的变量名称都可能不同;
~ 访问限制
在Class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样,就隐藏了内部的复杂逻辑;
注意将属性设置为private,避免外部代码自由地修改一个实例的name、score属性;
__xxx__
:以双下划线开头,并且以双下划线结尾的变量,是特殊变量,不是private变量,可以直接访问的,_name
:单下划线开头的变量,外部是可以访问的,但是,按照约定俗成的规定,视为私有变量,不要随意访问;__name
:双下划线开头的变量 是private变量,外部不能直接访问,但是也可以访问;因为Python解释器对外把__name
变量改成了_Student__name
,所以,仍然可以通过_Student__name
来访问__name
变量;注意:
>~ bart = Student('Bart Simpson', 59)
>~ bart.get_name()
'Bart Simpson'
>~ bart.__name = 'New Name' # 设置__name变量!
>~ bart.__name
'New Name'
表面上看,外部代码“成功”地设置了__name变量
,但实际上这个__name
变量和class内部的__name
变量不是一个变量!内部的__name
变量已经被Python解释器自动改成了_Student__name
,而外部代码给bart新增了一个__name
变量;
~ 继承和多态
文章整理自廖雪峰官方网站:https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000