[Python与数学建模-入门使用]-4函数
1自定义函数语法
Python中自定义函数的语法如下:
def functionName(formalParameters):
functionBody
(1)functionName是函数名,可以是任何有效的Python标识符。
(2)formalParameters是形式参数(简称形参)列表,在调用该函数时通过给形参赋值来传递调用值,形参可以有多个、一个或零个参数组成,当有多个参数时各个参数由逗号分隔;圆括号是必不可少的,即使没有参数也不能没有它。括号外面的冒号也不能少。
(3)functionBody是函数体,是函数每次被调用时执行的一组语句,可以由一个语句或多个语句组成。多个语句的函数体一定要注意缩进。
函数通常使用三个单引号'''...'''来注释说明函数;函数体内容不可为空,可用pass来表示空语句。在函数调用时,函数名后面括号中的变量名称称为实际参数(简称实参)。定义函数时需要注意以下两点:
(1)函数定义必须放在函数调用前,否则编译器会由于找不到该函数而报错。
(2)返回值不是必需的,如果没有return语句,则Python默认返回值None。
例1.5,求阶乘
定义求阶乘的函数如下,并保存在文件Pex1_5_1.py中。
def factorial(n):
r = 1
while n > 1: r *= n; n -= 1
return r
调用自定义函数factorial的程序如下:
from Pex1_5_1 import *
print(factorial(5))
也可以把函数的定义和调用代码写在一个文件中,具体如下:
def factorial(n):
r = 1
while n > 1: r *= n; n -= 1
return r
print(factorial(5)) #调用函数
2自定义函数的四种参数
2.1位置参数
函数调用时的参数通常采用按位置匹配的方式,即实参按顺序传递给相应位置的形参。这些实参的数目应与形参完全匹配。
2.2默认参数
认参数是指在构造自定义函数的时候已经给某些参数赋予了各自的初值,当调用函数时,这样的参数可以不用传值,默认参数必须指向不变对象。例如计算1到n的p次方和。
程序文件Pz1_14.py
def square_sum(n, p=2):
result=sum([i**p for i in range(1, n+1)])
return (n, p, result)
print("1到%d的%d次方和为%d"%square_sum(10))
print("1到%d的%d次方和为%d"%square_sum(10,3))
2.3可变参数
上面的必选参数和默认参数都是在已知这个自定义函数需要多少个形参的情况下构建的,如果不确定该给自定义函数传入多少个参数值时,就需要Python提供可变参数。
例如两个数的求和函数:
程序文件Pz1_15.py
def add(a,b): s=sum([a,b]); return (a,b,s)
print("%d加%d的和为%d"%add(10,13)) #输出:10加13的和为23
如果要求任意个数的和,必须使用可变参数,可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个元组。
程序文件Pz1_16.py
def add(*args): print(args, end=''); s=sum(args); return(s)
print("的和为%d"%add(10,12,6,8))
运行结果:
(10, 12, 6, 8)的和为36
如上自定义函数中,参数args前面加了一个星号*,这样的参数就是可变参数,该参数是可以接纳任意多个实参的。之所以能够接纳任意多个实参,是因为该类型的参数将这些输入的实参进行了捆绑,并且组装到元组中,就是自定义函数中print(args)语句的效果。
2.4关键字参数
虽然一个可变参数可以接受多个实参,但是这些实参都被捆绑为元组了,而且无法将具体的实参指定给具体的形参,那么有没有一种参数既可以接受多个实参,又可以把多个实参指定给各自的实参名呢?答案是关键字参数,而且这种参数会把带参数名的参数值组装到一个字典中,键就是具体的实参名,值就是传入的参数值。
程序文件Pz1_17.py
def person(name, age, **kw):
print('name:', name, 'age:', age, 'other:', kw)
person('Michael', 30)
person('Bob', 35, city='Beijing')
person('Adam', 45, gender='M', job='Engineer')
执行结果:
name: Michael age: 30 other: {}
name: Bob age: 35 other: {'city': 'Beijing'}
name: Adam age: 45 other: {'gender': 'M', 'job': 'Engineer'}
如上面程序所示,在自定义函数person中,name和age是位置参数,kw为关键字参数。当调用函数时,name和age两个参数必须要传入对应的值,而其他的参数都是用户任意填写的,并且关键字参数会把这些任意填写的信息组装为字典。
在Python中定义函数,可以用位置参数、默认参数、可变参数、关键字参数,这4种参数都可以组合使用。但是请注意,参数定义的顺序必须是:位置参数、默认参数、可变参数、关键字参数。
3参数传递
3.1参数传递方式
大多数程序设计语言有两种常见的参数传递方式:传值调用和传址调用。
(1)传值(call by value)调用:表示在调用函数时,会将自变量的值逐个复制给函数的参数,在函数中对参数值所做的任何修改都不会影响原来的自变量值。
(2)传址(pass by reference)调用:表示在调用函数时,所传递函数的参数值是变量的内存地址,参数值的变动连带着也会影响原来的自变量值。
在Python语言中,当传递的数据是不可变对象(如数值、字符串)时,在传递参数时,会先复制一份再进行传递。但是,如果所传递的数据是可变对象(如列表),Python在传递参数时,会直接以内存地址来传递。简单地说,如果可变对象在函数中被修改了内容值,因为占用的是同一个地址,所以会连带影响函数外部的值。以下是函数传值调用的范例。
程序文件Pz1_18.py
def fun(a,b):
a, b = b, a;
print("函数内交换数值后:a=%d,\tb=%d"%(a,b))
a=10; b=15
print("调用函数前的数值:a=%d,\tb=%d"%(a,b))
print("-----------------------------------")
fun(a,b) #调用函数
print("-----------------------------------")
print("调用函数后的数值:a=%d,\tb=%d"%(a,b))
执行结果:
调用函数前的数值:a=10, b=15
-----------------------------------
函数内交换数值后:a=15, b=10
-----------------------------------
调用函数后的数值:a=10, b=15
下面再举一个传址调用的范例,参数为列表,是一种可变对象。
程序文件Pz1_19.py
def change(data):
data[0], data[1] = data[1], data[0]
print("函数内交换位置后:",end='')
for i in range(2): print("data[%d]=%2d"%(i,data[i]),end='\t')
data=[16, 25] #主程序
print("原始数据为:",end='')
for i in range(2): print("data[%d]=%2d"%(i,data[i]),end='\t')
print("\n-------------------------------------------------------")
change(data)
print("\n-------------------------------------------------------")
print("排序后数据为:",end='')
for i in range(2): print("data[%d]=%2d"%(i,data[i]),end='\t')
运行结果:
原始数据为:data[0]=16 data[1]=25
-------------------------------------------------------
函数内交换位置后:data[0]=25 data[1]=16
-------------------------------------------------------
排序后数据为:data[0]=25 data[1]=16
3.2参数传递的复合数据解包
传递参数时,可以使用Python列表、元组、集合、字典以及其他可迭代对象作为实参,并在实参名称前加一个星号,Python解释器将自动进行解包,然后传递给多个单变量形参。但需要注意的是,如果使用字典作为实参,则默认使用字典的键,如果需要将字典中的键-值对作为参数则需要使用items()方法,如果需要将字典的值作为参数则需要调用字典的values()方法。最后,请保证实参中元素个数与形参个数相等,否则出现错误。
程序文件Pz1_20.py
def fun(a,b,c): print("三个数的和为:",a+b+c)
seq=[1,2,3]; fun(*seq) #输出:三个数的和为: 6
tup=(1,2,3); fun(*tup) #输出:三个数的和为: 6
dic={1:'a', 2:'b', 3:'c'}; fun(*dic) #输出:三个数的和为: 6
set={1,2,3}; fun(*set) #输出:三个数的和为: 6
4两个特殊函数
Python有两类特殊函数:匿名函数和递归函数。匿名函数是指没有函数名的简单函数,只可以包含一个表达式,不允许包含其他复杂的语句,表达式的结果是函数的返回值。递归函数是指直接或间接调用函数本身的函数。递归函数反映了一种逻辑思想,用它解决某些问题时显得很简练。
4.1匿名函数
例1.6 lambda函数的定义和调用示例。程序文件Pex1_6.py
f=lambda a,b=2,c=5: a-b+c #使用默认值参数
print("f=",f(10,20)) #输出: f=-5
print("f=",f(10,20,30)) #输出:f=20
print("f=",f(c=20,a=10,b=30)) #使用关键字实参,输出:f=0
图片.png
程序文件Pex1_7.py
f=lambda n,m:sum([k**m for k in range(1,n+1)])
s=f(100,1)+f(50,2)+f(10,-1)
print("s=%10.4f"%(s))
执行结果:
s=47977.9290
4.2递归函数
递归函数是指一个函数的函数体中又直接或间接地调用该函数本身的函数。如果函数a中又调用函数a本身,则称函数a为直接递归。如果函数a中先调用函数b,函数b中又调用函数a,则称函数a为间接递归。程序设计中常用的是直接递归。
图片.png
图片.png
程序文件Pex1_8.py
n=int(input("请输入n的值:"))
def fac(n):
if n<=1: return 1
else: return n*fac(n-1)
m=fac(n) #调用函数
print("%d!=%5d"%(n,m))
运行结果:
请输入n的值:6
6!= 720
图片.png
图片.png
图片.png
当一个问题蕴含了递归关系且结构比较复杂时,采用递归函数可以使程序变得简洁、紧凑,能够很容易地解决一些用非递归算法很难解决的问题。但递归算法是以牺牲存储空间为代价的,因为每一次递归调用都要保存相关的参数和变量。而且递归函数也会影响程序执行速度,由于反复调用函数,会增加时间开销。
5导入模块
随着程序的变大及代码的增多,为了更好地维护程序,一般会把代码进行分类,分别放在不同的文件中。公共类、函数都可以放在独立的文件中,这样其他多个程序都可以使用,而不必把这些公共的类、函数等在每个程序中复制一份,这样独立的文件就叫做模块。
标准库中有与时间相关的time、datetime模块,随机数的random模块,与操作系统交互的os模块,对Python解释器相关操作的sys模块,数学计算的math模块等几十个模块。要查看所有模块,可以使用命令
help("modules")
要看math模块的帮助,可以使用命令
import math; help(math)
要看math模块的所有函数,可以使用命令
import math; dir(math)
导入模块有四种方式:
5.1import 模块名 [as 别名]
使用这种方式导入以后,使用时需要在对象之前加上模块名作为前缀,即必须以“模块名.对象名”的形式进行访问。如果模块名字很长的话,可以为导入的模块设置一个别名,然后使用“别名.对象名”的方式来使用其中的对象。
程序文件Pz1_21.py
import numpy as np #导入numpy库,相当于大模块,并设置别名为np
import numpy.linalg as LA #导入numpy库下linalg(线性代数)模块,别名为LA
a=np.linspace(0,10,5) #产生0到10之间等间距的5个数
b=LA.norm(a) #求b的模,即向量a的长度
print("a的长度为:%7.4f"%b)
同时导入的模块有多个时,模块名字之间用逗号分隔。例如:
import time, random #导入基础库中的time和random模块
5.2from 模块名 import 对象名 [as 别名]
使用这种方式仅导入明确指定的对象,并且可以为导入的对象确定一个别名。这种导入方式可以减少查询次数,提高访问速度;同时也可以减少程序员需要输入的代码量,因为不需要使用模块名作为前缀。
程序文件Pz1_22.py
from numpy import random as rd #从numpy库中导入模块random并设置别名为rd
from math import sin, cos #导入模块中的正弦函数和余弦函数
from random import randint
a=rd.randint(0,10,(1,3)) #产生[0,10)的3个元素的随机整数数组
b=randint(0,10) #产生[0,10]上的一个随机整数,不能产生向量
print("sin(b)=%6.4f"%sin(b))
print("cos(b)=%6.4f"%cos(b))
其中一次运行结果:
sin(b)=0.9093
cos(b)=-0.4161
注1.8 注意import random和import numpy.random的差别,import random是导入Python基础库的random模块,import numpy.random是导入numpy库的random模块,建议以后使用函数时,尽量使用numpy库中的函数,它的函数可以对向量进行运算,而基础库中的函数一般对标量进行运算。基础库中的random.randint()函数无法产生向量,numpy库中的numpy.random.randint()函数可以产生向量。
5.3from 模块名 import *
这是第2种用法的一种极端情况,可以一次导入模块中通过_ all _变量指定的所有对象。使用这种一次导入库或模块中所有对象的方式固然简单省事,但是并不推荐使用,一旦多个模块中有同名的对象,这种方式将会导致混乱。建议使用什么函数就导入什么函数。
程序文件Pex1_10.py
from math import log, exp, sin, pi
f=lambda n:(1+log(n)+sin(n))/(2*pi)
y=exp(2)
for n in range(1,101): y += f(n)
print("y=%7.4f"%y)
运行结果:
y=81.1752
注1.9 Python的帮助和MATLAB的帮助是类似,看numpy库的模块和帮助信息,使用命令:
help(numpy)
或
help("numpy")
这里使用help("numpy")不需要预先加载numpy库,使用help(numpy)需要预先加载numpy库。
看numpy库中random模块的帮助使用命令:
help(numpy.random)或help("numpy.random")
可以看到numpy.random模块中所有对象的信息。如果只看numpy.random模块中的函数名使用命令
dir("numpy.random")
看numpy.random模块中的函数randint()的帮助使用命令:
from numpy.random import randint
help(randint)
要学会查询Python每个库中有哪些模块,每个模块有哪些函数。
5.4自定义模块的导入
通常用户将多个函数收集在一个脚本文件中,创建一个用户自定义的Python模块。
图片.png
程序文件FunctionSet.py
def f(x): return x**2+x+1
def g(x): return x**3+2*x+1
def h(x): return 1/f(x)
第一种调用模式:
程序文件Pex1_11_1.py
import FunctionSet as fs
print(fs.f(1),'\t',fs.g(2),'\t',fs.h(3))
第二种调用模式:
程序文件Pex1_11_2.py
print(f(1),'\t',g(2),'\t',h(3))