定义
将相对独立且可能需多次执行的一组语句作为一个整体封装起来形成一个函数。函数定义的一般形式为:
def 函数名(形式参数):
'功能描述字符串' # 可选, 函数.__doc__
函数体
return 值 # 可选
#如果想定义无参函数,就是将参数省略,但是括号不能省
#使用return时注意如下3种情况:
#(1)return语句可省略。如没有return, 则默认返回None
#(2)return语句指定返回多个值
#函数也可返回多个值。将多个值放在return的后面,逗号分隔。实质是返回一个元组
#(3)多个return语句
#函数体中可以有多个return语句,当执行其中任意一个return语句后,该函数的执行就结束了。
调用自己编写的函数
(1) 如函数已独立执行过,则内存中已存在此函数,在IPython 窗口中可以直接用 函数名(参数) 来调用。
(2) 也可像模块一样调用,程序文件名即模块名。例如,假定两个函数保存在 myfun.py 文件中,则调用格式为:
import myfun # 不需要.py,将此程序存于IPython当前工作目录
myfun.mysum(1,100)
myfun.fibonacci(6)
调用
函数调用的一般形式为:函数名(实际参数)
函数调用时一定要有小括号。没有小括号不会报告语法错误,但这不是调用函数,相当于函数重命名。
d = minus # 缺失括号,相当于给minus起了个别名 d
不可变对象和可变对象参数
Python数据分为不可变数据和可变数据。
数值、字符串和元组是不可变对象;
列表、集合和字典是可变对象。
不可变是指不可修改原内存单元的数据。不可变对象作为函数实参时,如果在函数体内改变形参的值,不会导致实参值发生改变
def change(a):
print('a1:', id(a))
a = a + 1
print('a2:', id(a))
print("a =", a)
b = 4
print('b: ', id(b))
change(b)
print("b =", b)
结果
b: 140734905570432
a1: 140734905570432
a2: 140734905570464
a = 5
b = 4
你看,参数进去的时候id地址还是一样的
但是当进行赋值的时候,参数的id就改变了,不是原本实参的id
可变对象作为函数实参时,在函数体内改变形参的值将导致实参发生改变。
就是形参和实参同一个东西
默认值参数
定义函数时,可以为形参设置默认值。
设置默认值后可以简化函数调用。Python的很多函数都有默认值参数。
例如: int(‘11’, base=10) ,后面的base=10就是默认参数是将字符串按照十进制的方式进行转化成十进制,如果将base=2就是按照二进制转化成十进制
sorted(lst, reverse=False)
def 函数名(…, 形参名=默认值):
函数体
定义默认值参数时,默认值参数的右边不能再出现没有默认值的参数。
def f(x, y, z=5) # 正确
f(1,2) 或 f(1,2,3) 均可
但是
def f(x, y=10, z) # 错误,y的右边不能再出现没有默认值的z
为什么不允许? f(1,2) -- x=1, y=2 ,z没有值 或 x=1, y=10 ,z=2,有歧义
而且写成 f(1,2,3),这时就是y=2了,之前的y是默认值10,所以就行不通的
深入讨论:一个形参如果设置了列表、字典等可变类型的默认值,那么第一次调用函数时,**形参的值被设为默认值(仅初始化一次),以后再调用函数就不会再次用默认值初始化。**例如:
def defaultPara(a, b = [ ]):
b.append(a)
print("b = ", b)
defaultPara(1, [2]) # 覆盖默认值
defaultPara(3)
defaultPara(4, [5])
defaultPara(6)
结果
b = [2, 1]
b = [3]
b = [5, 4]
b = [3, 6]
可以看到使用默认值的话就只会初始化一次,以后再用到默认值就会使用之前初始化后使用过的默认值
因为上面容易混淆,所以一般不提倡赋予 b=[ ]的默认值。为避免副作用,
建议b不写默认值或 b=None。这样如果不传递一个列表,将报错
关键字参数
通过关键字(命名)参数,在调用时指定形参的名字,可以实现实参不按照形参的顺序书写,不用记忆形参顺序,调用格式易于理解
def keyPara(a, b, c):
print(a+b*c)
print("a = {}, b = {}, c = {}".format(a, b,c))
keyPara(1, 2, 3) # 如果 keyPara(3,2,1) 结果不同
使用关键字参数,按如下几种方式调用,效果相同:
keyPara(a=1, b=2, c=3)
keyPara(c=3, b=2, a=1)
keyPara(1, b=2, c=3)
输出都是:
7
a = 1, b = 2, c =3
keyPara(1, b=2, 3) #这种格式错误, 关键字参数后面不能再有位置参数,不然报错
SyntaxError: positional argument follows keyword argument
不定长参数
如果在定义函数时不能确定形式参数的数量,那么需要定义不定长参数。
不定长参数的定义形式有两种。
第一种形式如下:
def 函数名(*形参名): # 注意 * 表示不定长
函数体
系统将传递进来的任意数量的实参组合成一个元组传给形参。
def varPara(*p):
print(p, type(p))
varPara(1, 2, 3)
varPara("a", [4, 5], "bc")
输出如下:
(1, 2, 3) <class 'tuple'>
('a', [4, 5], 'bc') <class 'tuple'>
第二种定义不定长参数的形式
def 函数名(**形参名): # ** 视为字典
函数体
系统将传递进来的任意数量的、显式赋值的关键字实参组合成一个字典传给形参。
def varPara(**p):
for it in p.items():
print(it)
varPara(a = 1, b = 2, c = 3) # 必须全部按关键字参数格式传递
输出如下:
(’a’, 1)
(’b’, 2)
(’c’, 3)
实参序列解包
实参序列解包是指当函数具有多个形参时,可以使用列表、元组、集合、字典及其他可迭代对象作为实参,并在实参名称前面加上一个星号 *,Python将自动对实参序列解包,将序列中的值分别传递给形参。如果实参是字典,那么需加两个星号 **
def varPara(x, y, z):
print("The sum is {}".format(x+y+z))
以下两行代码实现对列表解包。
a = [1, 2, 3]
varPara(*a) # 数量要匹配。 写为 varPara(a[0], a[1], a[2]) 较繁琐
varPara(a) # 错误
以下两行代码实现对字典解包。
dic = {'x':1, 'y':2, 'z':3}
varPara(**dic) # 要求字典的键名必须和形式参数名对应
变量的作用域
局部变量是在函数内部定义的变量,只在该函数内部起作用,作用范围从定义位置开始到函数结束。
形参变量也是局部变量,它的作用范围也只限于该函数内部。实参和形参可以同名
全局变量是指在函数外部定义的变量,或是在函数内用global关键字特别申明的变量。其作用范围从定义该变量的位置开始到程序结束。
a = 5 # 全局变量
def glob():
b = a # 使用了全局变量a, 此处的b局部
print("a = {}, b = {}".format(a, b))
glob()
如果要在函数体内部修改全局变量a的值,那么要用关键字global申明变量a
牢记:在函数内部被赋值,但又没有被 global申明的变量都是局部变量。
看一个错误例子
def m():
b = b + 1 # b局部
b=2
m(); m()
print(b)
报错:UnboundLocalError: local variable 'b' referenced before assignment
为什么会报错呢?
就是在b=b+1这里报错,因为在函数内部赋值的都是局部变量
那么函数内的b就是局部变量,但是b没有值的情况进行b+1,
所以就会报错
lambda表达式
lambda表达式用于定义匿名函数。有时我们需要使用一个函数,但可能仅使用一次,在这种场合下,无需正式定义一个普通函数,用lambda定义一个匿名函数即可
比如之前我们是这样写
sort这个函数里面的key就是排序的依据,排序的时候会将排序
的对象传进去,返回排序的依据
from random import randint, seed
def square(x):
return x**2
seed(1)
a = [randint(-10,10) for _ in range(10)]
print('排序前:', a)
a.sort(key = square) # 根据数据的平方值排序
print('排序后:', a)
而现在使用lambda表达式
from random import randint, seed
seed(1)
a = [randint(-10,10) for _ in range(10)]
print('排序前:', a)
a.sort(key = lambda x:x**2) # 用lambda表达更简洁
print('排序后:', a)
# 对学生列表排序的例子
stu = [('B', 90,85), ('A', 80,92), ('C',85,85), ('D', 99,89)]
s1 = sorted(stu, key=lambda x:x[1]) # 按成绩1排序
s2 = sorted(stu, key=lambda x:x[2]) # 按成绩2排序
s = sorted(stu) # 按姓名字母排序,因为默认就是按照元组的第一个元素排序的,所以可以不用写key
也可以将lambda表达式变成命名函数,格式如下:
函数名 = lambda 形参: 表达式
f = lambda x, y: x + y
f(2, 3)
注意:lambda表达式很简单,只支持单条语句,不支持条件和循环。
嵌套定义和修饰器
Python函数可以嵌套定义,即在函数体内再定义另一个函数。嵌套函数的定义形式与一般函数一样,只是要位于一个函数的函数体内,被嵌套的函数不会随着外部函数的执行而自动执行,要执行被嵌套的函数,需要在外部函数的函数体内显式地调用它.
def outer():
def inner():
print("inner function ")
inner() # 显示调用内部函数
print("outer function ")
outer()
它的输出是:
inner function
outer function
你可能问这样内部函数有什么用?
嵌套函数的一个重要应用是修饰器修饰器也是一个函数,它接收其他函数作为参数,在此基础上增加一些功能作为修饰并返回新函数。
就好比如你已经写了一个函数,已经实现了全部的功能,现在需要增加一些日志信息,但是你又不可能修改原本已经写好的函数,所以这时候就可以使用到嵌套函数的应用:修饰器
def decorator(fun): # 修饰器, 参数是一个函数名
def wrapper(*args, **kwargs): # 内部函数
print("前修饰操作", time.ctime())
result = fun(*args, **kwargs)
print("后修饰操作")
return result
return wrapper # 此处返回的是修饰后的新函数
@decorator # @修饰标识
def main():
print("主要功能")
在函数main前加上@ decorator以使用该修饰器,
这样main就成为修饰后的新函数,名称不变。调用函数main的方法与原来一样。
参数:*args, **kwargs就是可以传递任意类型的任意数量的参数,
前面表示任意数量的序列类型,后面表示任意数量的非序列的字典类型
加了修饰器就是在函数执行前后添油加醋
例如 main() ,输出为:
前修饰操作 Tue Mar 17 15:36:46 2020
主要功能
后修饰操作
修饰器是一种高级编程方法,常见于框架/架构等系统内,可用于日志/授权/性能测试等。修饰器的作用就是为已经存在的函数对象添加额外的功能。
生成器函数
生成器函数是一种内含yield语句的特殊函数,用于创建生成器对象。
生成器对象特点:1.惰性机制, 2.只能向前, 3.节省内存。
例如需要产生1亿个数据,普通函数要将1亿个数据都产生并返回,内存耗费巨大,而采用生成器对象可以每次只生成1个,处理后再生成下一个,节省内存。
def g(): # 生成器函数
a = 1
while True:
yield a
a = a + 2
c = g() # c是生成器对象(generator), type(c)
print(next(c)) #1
print(next(c)) #3
print(next(c)) #5
yield语句返回一个值并暂停函数的运行,下次通过内置函数next或for循环遍历生成器对象时再恢复执行,并从yield语句的后一条语句继续执行。
#下面的语句通过for循环遍历生成器对象,输出一些奇数。
def g(): # 生成器函数
a = 1
while True:
yield a
a = a + 2
d = g() # 生成器对象
for i in d:
print(i, end = ' ') # 输出 1 3 5 7.... 11
if i >= 10:
break
#再一次调用
print('------')
for i in d:
print(i, end = ' ') # 仅输出 13,没有之前的1 3...11,因为生成器对象只能向前,不能回溯,就是像前面说的,它只是挂起,没有终止
if i >= 10:
break
产生生成器对象的另一种语法: (推导式)
推导公式是使用一对括号生成一个生成器对象
lst = [1, 2, 3, 4]
g = (2*x for x in lst) # (推导式) 将产生一个生成器对象
for x in g:
print(x, end = ' ') # 输出 2 4 6 8
for x in g:
print(x, end = ' ') # 没有任何输出,因为 g在上次已耗尽
函数递归调用
一个函数自己调用自己称为递归调用。
递归可分为:直接递归和间接递归。递归一定要注意终止递归的条件。
直接递归就是一个函数直接调用该函数自身。
间接递归就是一个函数调用另一个函数,该函数又调用原函数。
#编写递归函数 f(n),把十进制整数n转换为二进制数输出。
def f(n):
if n == 0: # 当n为0或1时,无须再递归,可返回'0'或'1'
return '0'
elif n==1:
return '1'
t = n % 2 # 余数
N = n // 2 # 整除, 得到 N
return f(N) + str(t) # 递归调用
#用递归写一个求和函数,可传入任意个数的参数。
def sum2(*p):
if len(p) == 0:
return 0
else:
if type(p[0])== range:
return sum2(*list(p[0])) + sum2(*p[1:])
elif type(p[0]) in (list,tuple):
return sum2(*p[0]) + sum2(*p[1:])
else:
return p[0] + sum2(*p[1:])
这里的*p就是可以传递任意数量的参数,
sum2(*list(p[0]))就是将可迭代对象变成列表,
然后进行实参序列解包,相当于将可迭代对象的每个元素分开传递给参数
如果直接是参数或者列表,就第一个加后面的和
pyinstaller库
pyinstaller库的作用是将Python程序转换为可执行程序,可脱离python环境独立运行。
pip install pyinstaller
国外的软件仓库网速慢,不稳定,可用 i 参数指定国内站点
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pyinstaller
pip 本身也分版本,pip版本低可能导致安装失败。升级pip的命令为:
python -m pip install -i https://pypi.tuna.tsinghua.edu.cn/simple --upgrade pip
pyinstaller不需要import导入,而是在命令行窗口中使用。格式如下:
pyinstaller [选项] python源文件
个Python项目包含多个源文件时,只需在pyinstaller命令行中指定包含程序入口的源文件。例如,若源文件为target.py,则将该程序转换为可执行程序的命令如下:
pyinstaller -F -c target.py
-F选项指定生成单独的EXE文件,-c选项指定使用命令行窗口运行程序。执行这个命令的时候需要切换到这个py文件对应的目录地址上去执行
如果Py程序含有图形界面(例如使用了tkinter库),则转换命令为:
pyinstaller -F -w target.py
-w 选项指定使用图形窗口运行程序
jieba库
在进行文字分析时,分词是必做的工作。英文由于以空格为分隔,可以用s.split()分词,但split()无法对中文分词。jieba库的作用就是对中文文章进行分词,得到中文词语列表。
pip install jieba
或
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple jieba
分词时主要使用cut() 和 lcut()函数,它们的使用格式如下:
import jieba
s = '我在北京大学'
g = jieba.cut(s, cut_all=False) # g 生成器对象
word = list(g) # ['我', '在', '北京大学']
lst = jieba.lcut(s, cut_all=True) # ['我', '在', '北京', '北京大学', '大学']
cut_all参数为True表示全模式分词,为False表示精确模式分词,默认为False。
全模式下,字符串中的所有可能词语都会被提取出来。
在精确模式下,只提取字符串中最长的一个词语。
wordcloud库
wordcloud库的功能是将一组给定的词语按照词频转换为一张图片,高频词显示的占比较大,从而凸显文章大意。安装命令:
pip install wordcloud
使用wordcloud库来生成词云,基本分三步:
from wordcloud import WordCloud
# 第一步 :构造一个WordCould对象
wc = WordCloud(width = 1000, height = 800, background_color='white') #background_color默认是黑色
# 第二步 :利用该对象的generate()生成词云图
wc.generate(字符串) #参数必须是用空格分隔的字符串 'AA BB AA CC'
# 第三步 :利用该对象的to_file()将词云图保存为图片文件(png, jpg等)
wc.to_file('tu.png')
# 英文词云图示例
from wordcloud import WordCloud
s = 'With the improving of semiconductor technology, a single chip integrates more and more processing cores. Highly parallel applications are distributed to tens of processing units. The inter-processor communication delay becomes more and more important for parallel applications.'
wc = WordCloud(background_color='white', width=1000, height=800)
wc.generate(s)
wc.to_file('wc.png') # 生成的图片默认保存在程序所在的目录
# 也可用语法糖格式写为
WordCloud(background_color='white', width=1000, height=800).generate(s).to_file('wc.png')
# 在IPython中执行下面命令可显示图片,也可以直接看图软件打开
from IPython.display import Image
Image('wc.png')
如果是中文文章,那么首先用jieba分词,并用空格连接分词,得到以空格分隔的字符串,然后再用WordCloud生成词云。
import jieba
str = '编写程序就是用计算机语言实现算法的过程。可以证明,任何问题都可以由三种基本结构表达出来。这三种基本结构包括顺序结构、选择结构和循环结构。'
jbstr = ' '.join(jieba.lcut(str)) # 用空格将分词列表连接为 字符串
wc = WordCloud(font_path='simkai.ttf', background_color='white',
width=500, height=400) # 楷体,指定中文字体
wc.generate(jbstr) # 生成词云
wc.to_file('wc2.png') # 存为wc2.png
#另一方法:按频次生成, dic是一个字典,数据类似 {'a':10, 'b':21, 'c':30}
WordCloud().generate_from_frequencies(dic).to_file('w.png')
turtle库
turtle库是Python内含的一个简易绘图库,可直接使用。它通过模拟一只乌龟在画板上的爬行来绘制图形。适合初学者练习编程。
画板采用笛卡儿坐标系,原点(0, 0)位于画板中央。初始,乌龟位于原点,朝向X轴的正方向。只需设定乌龟爬行的方向(常用seth /left /right )和距离(fd ),就能沿指定的方向绘制一条直线或曲线。距离单位为像素。
#画一个边长200像素的矩形
import turtle as t
t.speed(2) # 较慢速
for _ in range(4):
t.fd(200) # 绘制200像素长的线条
t.right(90) # 每次右转90度
t.done() # 结束绘图,spyder中需要此句
#turtle和Spyder似乎不太兼容,运行时每两次会报一次 Terminator错。同样的代码在IDLE中没问题。
turtle常用函数
看看就行,一半用不到
forward()或fd():向前移动指定的距离,参数为数值,单位像素。
right()或rt():以角度单位向右(顺时针)转动,相对目前的方向进行偏移。
left()或lt():以角度单位向左(逆时针)转动,相对目前的方向进行偏移。
goto()或setposition():移动到绝对坐标位置。
setheading()或seth():设置乌龟前进的绝对角度方向。
penup():抬起画笔,这样移动笔时就不会画出线条。
pendown():放下画笔,这样绘图时将画出线条。
done():表示绘图结束。(注:在spyder中用turtle库绘图时,结束时应执行此函数,如无则绘图窗口关闭时将不响应)
backward()或bk():向后移动指定的距离。
setx():设置乌龟沿X轴方向移动的距离,参数为数值。
sety():设置乌龟沿Y轴方向移动的距离,参数为数值。
home():将乌龟移回原点(0,0),并将方向设置为初始的正东方向,无参数。
circle():从当前点出发绘制一个给定半径的圆或圆弧。
dot():以当前点为圆心画一个给定直径的圆点。
pencolor():设置笔的颜色,参数为一个表示颜色的字符串。
pensize():设置画笔的宽度,参数为一个数值。
speed():设置绘图速度。0最快, 1-9 依次递增; ‘slow’, ‘fast’
看个例子
绘制太阳花图案。
import turtle as t
t.speed(0) # 速度0最快, 1-9 逐步加快
t.color("red", "yellow") # 设置线条颜色和填充颜色
t.begin_fill() # 开始填充
for _ in range(50):
t.forward(200) # 画200像素线条
t.left(170) # 左转170度
t.end_fill() # 结束填充
t.done()