python3中的函数与参数及空值问题
画星星
程序2-7-7主要使用turtle.forward前进操作和turtle.left左转操作在屏幕上画星星。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#2-7-8.py
import turtle
turtle.color('Green','yellow')
while True:
turtle.forward(200)
turtle.left(150)
print(turtle.pos())
if abs(turtle.pos()) < 1:
break
print('按回车键退出')
input()
程序2-7-7.py运行结果如下:
同时,程序2-7-7.py还输出了如下所示的线转角处(绘制一条直线后转角绘制另一个直线,2条直线的交点)的位置信息:
(200.00,0.00)
(26.79,100.00)
(126.79,-73.21)
(126.79,126.79)
(26.79,-46.41)
(200.00,53.59)
(0.00,53.59)
(173.21,-46.41)
(73.21,126.79)
(73.21,-73.21)
(173.21,100.00)
(0.00,-0.00)
按回车键退出
现对程序2-7-7.py分析如下:
1、该程序使用了以下几个函数:
(1)forward函数前进200,用于绘制直线。
(2)left函数左转150度,用于转角,绘制另一条直线。
(3)turtle.pos()返回当前位置坐标 (x,y) (坐标为 Vec2D 矢量类对象)。
(4) abs函数返回一个数的绝对值。 参数可以是整数、浮点数或任何实现了 abs() 的对象,当参数是一个复数时,返回它的模。在该程序中, abs的作用如下:
使用pos返回一个当前坐标后,abs求该Vec2D坐标到原点的距离(从原点出发的向量长度)。矢量空间内的所有矢量赋予非零的正长度或大小,在二维的欧氏几何空间 R中定义欧氏范数,在该矢量空间中,元素被画成一个从原点出发的带有箭头的有向线段,每一个矢量的有向线段(向量)的长度即为该矢量的欧氏范数。
由于Vec2D是一个二维矢量类,用来作为实现海龟绘图的辅助类,也可以在海龟绘图程序中使用,它派生自元组,因此矢量也属于元组。Vec2D主要提供以下运算 (a, b 为矢量, k 为数值):
a + b 矢量加法
a - b 矢量减法
a * b 内积
k * a 和 a * k 与标量相乘
此外,Vec2D类还实现了 abs操作,如下面代码片断所示(摘自Vec2D类源代码)。
class Vec2D(tuple):
...
def __abs__(self):
return (self[0]**2 + self[1]**2)**0.5
2、程序2-7-7.py的执行过程如下:
(1)通过turtle.color(‘Green’,‘yellow’)函数设置颜色为绿色画笔、黄色填充。
(2)创建循环体,循环体内容为:
首先,将海龟定位于原点。
然后,turtle.forward(200)前进200步,turtle.left(150)左转150度,print(turtle.pos())打印出当前海龟位置。
最后,通过abs(turtle.pos())判断当前向量(从原点到当前海龟位置的向量)的长度,如果长度<1,说明当前位置已经回到了起点(原点),就退出循环。如果长度>=1,则继续循环。
空值None
Python None 对象,表示缺乏值、空值。
下面代码定义了x和y共2个变量,其中,x初始化值为0,而y设为了空值,x虽然为0,但仍然属于有值状态,而y属于空值状态。
>>>x=0
>>>y=None
>>>x==None
False
>>>y==None
True
函数与Lambda
一、函数定义
Python定义函数使用关键字 def,后跟函数名与括号内的形参列表。函数语句从下一行开始,并且必须缩进。
程序2-7-10-1.py定义了函数getSum,完成参数求和后返回 。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#2-7-10-1.py
def getSum(x1,x2,x3,x4):
y=x1+x2+x3+x4
return y
print(getSum(11,22,33,44))
程序2-7-10-1.py执行结果如下:
110
程序2-7-10-1.py执行过程如下:
1、定义函数getSum,使用def关键字+函数名getSum的方式进行定义,参数为括号内的4个变量x1、x2、x3、x4。
函数的执行体为函数定义的下一行,共2行。
(1)第1行y=x1+x2+x3+x4,将4个参数之和赋值给变量y。其中,y是函数内局部变量,而x1、x2、x3、x4为函数局部变量。
(2)第2行return y,将y返回,return语句功能是是函数返回值,没有 return 语句的函数也返回值,只不过这个值是 None。
2、调用getSum函数,并打印函数返回结果。
二、引用变量查找
函数在执行时使用函数局部变量符号表,所有函数变量赋值都存在局部符号表中;引用变量时,其查找顺序为:首先,在局部符号表里查找变量,然后,是外层函数局部符号表,接着是全局符号表,最后是内置名称符号表。因此,虽然可以引用全局变量和外层函数的变量,但是最好不要在函数内直接赋值(除非是 global 语句定义的全局变量,或 nonlocal 语句定义的外层函数变量)。
程序2-7-10-2.py定义了函数getSum,完成参数求和后返回,与2-7-10-1.py基本相同,不同之处在于,在getSum函数体内出现了变量y,而在getSum函数体外也出现了变量y。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#2-7-10-2.py
y=9
def getSum(x1,x2,x3,x4):
y=x1+x2+x3+x4
return y
print(y)
print(getSum(11,22,33,44))
print(y)
程序2-7-10-2.py的执行结果为:
9
110
9
观察程序2-7-10-2.py的执行结果,可发现,虽然,在getSum函数定义之前(之外),已经定义了变量y=9,随后又在getSum函数体内将变量y的值改为了4个参数之和110,那么在执行完getSum函数后,y的值会变为110,但在此处却仍没有变化,还是9。这是什么原因呢?答案是:getSum函数体内的y存在于函数局部变量符号表内,而getSum函数定义之外的y属于全局变量,2个y不属于同一变量,因此,对getSum函数体内的y值进行的修改并不影响函数体之外定义的y的值。
那么,如果一定要在getSum函数体内对函数体外定义的y进行修改,可按程序2-7-10-3.py所示的方法进行。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#2-7-10-3.py
y=9
def getSum(x1,x2,x3,x4):
global y
y=x1+x2+x3+x4
return y
print(y)
print(getSum(11,22,33,44))
print(y)
程序2-7-10-3.py的执行结果为:
9
110
110
程序2-7-10-3.py与程序2-7-10-2.py基本一样,只是在函数体内通过global y,将y声明为全局变量,表示在函数体内使用的y就是全局变量y,并非函数体局部变量。
除了global还有一个语句nonlocal。nonlocal 语句会使得所列出的名称指向之前在最近的包含作用域中绑定的除全局变量以外的变量。 这种功能很重要,因为绑定的默认行为是先搜索局部命名空间。 这个语句允许被封装的代码重新绑定局部作用域以外且非全局(模块)作用域当中的变量。
与 global 语句中列出的名称不同,nonlocal 语句中列出的名称必须指向之前存在于包含作用域之中的绑定。程序2-7-10-4.py演示了nonlocal的用法。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#2-7-10-4.py
def printY():
y=9
print(y)
def getSum(x1,x2,x3,x4):
nonlocal y
y=x1+x2+x3+x4
getSum(100,2,3,4)
print(y)
printY()
程序2-7-10-4.py的执行结果为:
9
109
对程序2-7-10-4.py分析如下:
1、该程序有2个函数体,这2个函数体是嵌套关系,即:getSum嵌套在printY内部。
2、对于getSum函数来说,在printY函数内部定义的y属于局部作用域以外且非全局(模块)作用域当中的变量,且属于最近的包含作用域(getSum包含在printY作用域内)中,getSum函数想访问printY函数的变量,需要声明该变量为nonlocal变量。
3、在getSum函数体内对printY函数体定义的y值的修改是有效的。因此,程序执行后,第一次打印出y的值为初始值9,第二次修改后再打印出y的值为修改后的值109。
三、传值调用函数
在调用函数时Python会将实际参数(实参)引入到被调用函数的局部符号表中,因此,函数参数都是使用按值调用来传递的,所谓按值传递,会将值进行复制生成函数的局部变量。当一个函数调用另外一个函数时,会为该调用创建一个新的局部符号表。
但是,有一点要特别注意,Python中的对象分为可变对象与不可变对象,比如整数288属于不可变对象,而列表[288,11]、[288,]就属于可变对象。具体来说,strings、tuples和numbers是不可变对象,而list、dict等则是可变对象。
对于不可变对象的按值传递就是直接复制其值,而可变对象的按值传递是复制其引用,因为可变对象的值在Python内部实质表现为一个指向该对象的引用(内存地址,可理解为指针),顺着这个引用才可在内存中找到该对象真正的值。程序2-7-10-5.py演示了参数传递的用法。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#2-7-10-5.py
def swapNum(x,y):
temp=x
x=y
y=temp
def swapNums(nums):
temp=nums[0]
nums[0]=nums[1]
nums[1]=temp
x=11
y=22
swapNum(x,y)
print(x,y)
n=[11,22]
swapNums(n)
print(n)
程序2-7-10-5.py的执行结果如下:
11 22
[22, 11]
对程序2-7-10-5.py分析如下:
1、该程序定义了2个函数swapNum和swapNums。
(1)swapNum函数想完成的功能是将传入的2个参数值进行修改,完成互换,要能完成对调用swapNum的函数实际参数进行修改,而不是对函数局部变量进行修改。
(2)swapNums函数想完成的功能是对传入的列表进行修改,将其第0个元素和第1个元素进行互换,并保证修改效果影响到调用swapNums函数的变量。
但是,虽然都是传值传递参数,但是swapNum函数的参数属于不可变变量,swapNum函数体会复制它的2个参数,形成属于自己的函数内部局部参数,实质已经与调用swapNum的函数参数无关,修改是无效的;而swapNums函数的参数是列表,属于可变变量,swapNums函数也会复制参数的值,但这个参数值是列表的地址(引用),那么传递到swapNums函数体内部的参数就是该列表的引用,对该引用的修改直接影响到调用swapNums的函数,修改是有效的。
2、该程序定义整型变量x和y后,将其作为参数传入swapNum,想完成x和y的互换, 这一操作是失败的,无法成功,然后该程序又定义了列表n,将其作为 参数传入swapNums,想完成第0个元素与第1个元素的互换,此操作是成功的。观察程序2-7-10-5.py的执行结果可验证这一结论。
四、函数对象
函数定义在当前符号表中把函数名与函数对象关联在一起。解释器把函数名指向的对象作为用户自定义函数。还可以使用其他名称指向同一个函数对象,并访问访该函数。程序2-7-10-6.py演示了函数对象的用法。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#2-7-10-6.py
def swapNums(nums):
temp=nums[0]
nums[0]=nums[1]
nums[1]=temp
swap=swapNums
n=[11,22]
print(n)
swap(n)
print(n)
程序2-7-10-6.py的执行结果如下:
[11, 22]
[22, 11]
程序2-7-10-6.py首先定义了swapNums函数及其函数体内容;然后,定义一个变量swap,将swapNums函数赋值给变量swap(Python中的变量属于动态类型,对象的类型是可改变的),这样,swap成为了一个函数对象;最后,程序直接使用swap作为函数名调用swapNums函数。
五、函数文档
函数内的第一条语句可以是字符串,其意义在于:该字符串是DocString(文档字符串),利用文档字符串可以自动生成在线文档或打印版文档,还可以让开发者在浏览代码时直接查阅文档,Python 开发者最好可以养成在函数语句下一行中加入文档字符串的好习惯。程序2-7-10-7.py演示了函数文档的用法。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#2-7-10-7.py
def swapNums(nums):
"""
swapNums函数接受一个列表对象,
完成将列表对象的2个元素互换位置的功能。
"""
temp=nums[0]
nums[0]=nums[1]
nums[1]=temp
print(swapNums.__doc__)
print("---------------------")
help(swapNums)
程序2-7-10-7.py的输出结果如下:
swapNums函数接受一个列表对象,
完成将列表对象的2个元素互换位置的功能。
Help on function swapNums in module main:
swapNums(nums)
swapNums函数接受一个列表对象,
完成将列表对象的2个元素互换位置的功能。
分析程序2-7-10-7.py,该程序的执行过程如下:
1、定义swapNums函数,函数功能为:互换列表的2个元素。
(1)在函数体的第1行(即:def定义语句的下一行)开始用三引号标注了一段字符串,该字符串(是DocString(文档字符串),用于说明函数功能和用法等事项。在Python中单引号和双引号都可以用来表示一个字符串,而三单引号和三双引号包围的字符串可保持原来的格式。
(2)函数体的第5行开始,是函数体的代码段,完成函数体的功能。
2、通过函数对象.doc(注意前后都是双_)属性,提取将DocString特性,该程序通过print(swapNums.doc)将函数swapNums的文档字符串打印在屏幕上。
3、通过help()调用提取DocString属性并打印在屏幕上。该程序通过help(swapNums)将函数swapNums的文档字符串打印在屏幕上。
2.2.2 Lambda
myfun1= lambda a,b:math.sqrt(pow(a,2)+pow(b,2))
myfun2=lambda x:1 if (x%2==0) else 0
print(myfun1(12,19))
print(myfun2(193))
旋转角度
程序2-7-11.py演示了以30度为步长逐渐增大角度进行旋转,从0度开始直到360度为止。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#2-7-11.py
import turtle
turtle.color('Green','yellow')
lineLine=50
for i in range(0,361,30):
turtle.home()
turtle.left(i)
turtle.forward(lineLine+i)
turtle.write(f"{i}>[{turtle.position()[0]},\n{turtle.position()[1]}]")
turtle.home()
turtle.color('Black','red')
turtle.write(f"[{turtle.position()[0]},\n{turtle.position()[1]}]")
print('按回车键退出')
input()
程序2-7-11.py执行结果如下:
程序2-7-11.py的执行过程如下:
1、定义颜色为:画笔绿色,填充黄色,设定直线初始长度lineLine为50。
2、for循环绘制直线,i为旋转角度,从0度开始以30度为步长递增,直到360度结束,每递增一次就是一个循环。
(1)turtle.home()将位置和初始方向进行初始化,即:位置为(0.,0.),方向为0度。
(2)左转i度。
(3)forward绘制直线,每循环1次,直线长度递增30.
(4)在直线的一端(除开原点的另一个端),使用write绘制文本,文本内容为该直线端的位置。
3、循环结束,以黑色画笔(红色填充)绘制原点,并绘制原点坐标。
绘制函数图形
程序2-7-12.py演示了绘制一元二次函数的图像。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#2-7-12.py
import turtle
def getFunY(a,b,c,x):
y=a*x*x+b*x+c
return y
x=-10
a=0.6
b=0.2
c=0.8
while x<=10:
turtle.up()
turtle.goto(x,getFunY(a,b,c,x))
turtle.dot(2)
x+=0.1
input()
程序2-7-12.py的执行结果如下:
程序2-7-12.py的执行过程如下:
1、导入turtle库,为绘图做准备。
2、定义getFunY函数,接收参数a、b、c以及x,函数体的代码完成根据一元二次函数值的计算。
3、定义a、b、c、x值,其中x值初始化为-10
4、while循环,循环条件是x<=10。
(1)up函数抬起画笔
(2)goto函数移动到(x,y)处,其中x每次循环递增0.1,y为根据a、b、c、x值计算的getFunY函数的返回值。
(3)dot函数落笔画点。
x递增0.1,整个循环x的值从-10增加到10。