前篇
前面的两片文章,简单的介绍了,python的数据类型,包括数字,字符串、列表、元组、集合和字典,以及判断if else和循环 while,for的使用。并且我们已经可以是用这些功能来编写一些小程序了,但是如果只有这些功能,编写一些较大的程序会非常吃力。在写程序的过程中我们难免会碰到一些公共的算法和逻辑,看下面这个小例子:
#假设我们有这么一个需求,计算两个数10和20的平方和。
a = 10
b = 20
result = a ** 2 + b ** 2
print(result)
结果为:500
现在我们要增加一个需求,计算另外两个数的平方和,那么我们只能这么办:
a = 10
b = 30
result = a ** 2 + b ** 2
print(result)
结果为:1000
比较以上上面的两段程序,我们发现其实两段程序基本上是相同的,我只不过又复制了一遍,换了两个值而已。难道每换两个值就得重写一遍程序?当然,如果是这样,程序员就就骂街了。所以函数便应运而生了。简单的说函数就是一组算法,封装了固定的逻辑。我们把常用的算法整合成一个函数,在用到这个算法的时候我们不用重新写重复的代码,这就提高了代码的利用率。比如print就是一个函数,实现了打印输出的逻辑,我们每次打印,只是调用print函数而已。
函数的结构
我们可以定义自己的函数,函数的结构如下:
def functionName(arg1,arg2,....):
函数体
return result
- def:是定义函数的关键字,电脑看到这个关键字,就知道后面的内容是一个函数。
- functionName:是我们定义函数的名字
- 括号中的是函数的参数,参数可以为空,我们叫这里的参数为形参
- 函数体:是函数的逻辑算法
- return:是函数的返回值,函数默认返回None。
定义了函数是为了使用的。怎么使用函数呢,直接使用函数名和参数(这里的参数我们叫做实参),像下面这样,我们称作函数的调用:
functionName(arg1,arg2,....)
第一个函数
现在我们写一个计算两个数平方和的函数,如下:
#定义函数
def squareSum(num1,num2):
print(num1,num2)
return num1 ** 2 + num2 ** 2
#函数的调用
result = squareSum(10,20)
print(result)
result = squareSum(10,30)
print(result)
结果为:
10 20
500
10 30
1000
我们使用def定义了一个函数名字叫squareSum,有两个参数num1和num2,函数体输出两个参数,并返回两个值的平方和。之后我们就可以随意调用此方法,传给函数适当的参数值,返回值就会赋给result引用。
传递参数
参数传递有两种,一种的值传递,一种是引用传递。值传递的原理是赋值当前参数的副本,然后传递给函数,引用传递是传递引用指向的内存地址,需要先讨论一下python中的可变对象和不可变对象。字符串,元组,和数字都是不可变对象,列表,集合和字典都是可变对象。
python中的参数传递,我们可以简单的认为就是值传递(这和java是一样的),如果是传递的是不可变对象,那么传递的就是对象值的副本,在函数中修改了此参数值并不会影响到原来的值。如果传递的是可变对象,那么传递的就是引用的副本,原来的引用的副本引用是指向同一个内存地址的,所以说函数内修改了可变对象的内容,会影响到原来引用的值。看下面的例子:
num = 1
def update(num):
num = 2
print(num)
update(num)
print(num)
结果为:
2
1
函数中的num和函数外的num并不是一个num引用,只是一个副本,所以函数中修改num的值,并不会影响函数。
再看下面这个例子:
list = [1,2,3,4]
def updateList(list):
list = [5,6,7,8]
print(list)
updateList(list)
print(list)
结果为:
[5, 6, 7, 8]
[1, 2, 3, 4]
这个例子和上面的例子道理是一样的,改变的只是副本引用的值,并不影响原来的引用。
继续看下面的例子:
list = [1,2,3,4]
def updateList(list):
list.append(5)
print(list)
updateList(list)
print(list)
结果为:
[1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
从输出结果我们看到,函数里的修改影响到了原来的引用。因为这里修改的并不是引用,而是修改引用指向的对象的内容。原来的引用和副本引用都是指向同一个对象,所以肯定会影响到原来的引用。
参数
python的函数有这几种类型,固定参数,关键字参数,默认参数,变长参数,固定参数是指,必须按照参数定义的顺序来传递参数,且参数个数不能少,比如:
def myFunction(name,age,sex):
print(name,age,sex)
myFunction("a",20,1)
关键字参数,是指在调用函数的时候用关键字指定参数的值,这样就不必完全按照参数的顺序来传递参数,但是参数的个数还是不能少,比如:
def myFunction(name,age,sex):
print(name,age,sex)
myFunction(sex = 1, age=20, name="a")
默认参数,是指在定义函数的时候,给参数一个默认值,这样在调用函数的时候,这个参数可以省略而使用默认值。比如:
def myFunction1(name,age,sex=1):
print(name,age,sex)
myFunction1("a",20)
需要注意的是,默认参数使用时需要注意别产生歧义了。看下面这个函数
def myFunction2(name,age=1,sex):
print(name,age,sex)
这个函数执行时会报错,因为这个函数在调用时存在歧义,如果你这么调用myFunction2(“a”,1),就会让计算机很迷茫,你是少传了一个参数sex呢?还是要把1传递给sex?所以非默认参数,不能出现在默认参数之后。
变长参数是指这个参数可以接受多个值,如下:
def myFunction3(name,age,sex,*str):
print(name,age,sex)
for s in str:
print(s)
myFunction3("a",20,1,"a","b","c")
匿名函数
python 使用 lambda 来创建匿名函数。所谓匿名,意即不再使用 def 语句这样标准的形式定义一个函数,结构如下:
lambda arg1,arg2,....:expression
例子如下:
sum = lambda num1,num2: num1 + num2
print(sum(1,2))
输出结果:
3
变量作用域
python变量的作用域有4中
- L:局部作用域
- E:闭包函数外的函数中
- G:全局作用域
- B:内建作用域
x = int(2.9) # 内建作用域
g_count = 0 # 全局作用域
def outer():
o_count = 1 # 闭包函数外的函数中
def inner():
i_count = 2 # 局部作用域
L小于E小于G小于B。python中会按照这个顺序来寻找变量。python 中只有模块(module),类(class)以及函数(def、lambda)才会引入新的作用域,其它的代码块(如 if/elif/else/、try/except、for/while等)是不会引入新的作用域的,也就是说这这些语句内定义的变量,外部也可以访问,如下代码:
if 1 == 1:
msg = "hello"
print(msg)
输出结果:
hello
因为python没有块级的作用域,msg虽然是在if块中定义的变量,但是他依旧是个全局的变量。
再看下面这个例子:
def function1():
name = "name"
print(name)
运行报错:
Traceback (most recent call last):
File "E:\workspace-python\firstpython\src\first.py", line 4, in <module>
print(name)
NameError: name 'name' is not defined
是因为name是一个局部变量,不能再全局中找到。
再看一个例子:
name = "name"
def function2():
print(name)
结果:
name
函数中先在局部找name变量,没有找到,然后再继续找全局作用域,找到name变量。
继续看下面的例子:
name = "name"
def function3():
name = "aaa"
print(name)
function3()
print(name)
结果:
aaa
name
函数中的name是一个局部变量,函数外的name是一个全局变量,两个名字虽然相同,单不是同一个变量,不能混淆,如果在函数中要改变全局变量的值,需要使用global,如下所示:
name = "name"
def function4():
global name
name = "aaa"
print(name)
function4()
print(name)
输出结果:
aaa
aaa
通过global指定后,函数中对name的修改,就是修改的全局变量name.