要点概括:

Function-contents

  1. 函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。

  2. 函数能提高应用的模块性和代码的重复利用率。

  3. 我们可以使用Python提供的内建函数,如print(),也可以自己创建自定义函数。

1.定义函数

在定义函数的时候需要注意一下规则:

  • 函数代码块以def关键词开头,后接函数标识符名称和圆括号()
  • 任何传入参数和自变量必须放在圆括号内,圆括号之间可以用于定义参数。
  • 函数的第一行语句可以选择性地使用文档字符串,该字符串用于存放函数说明。
  • 函数内容以冒号起始,并且缩进。
  • 可以使用return [表达式]结束函数,选择性地返回一个值给调用方。不带表达式的return相当于返回None

定义一个函数用def关键字,格式如下:

1
2
def 函数名(参数列表):
    函数体

默认情况下,参数值和参数名称是按函数声明中定义的顺序匹配起来的。下面定义一个函数用来输出Hello World!,如下所示:

1
2
3
4
def hello():
    print('Hello World!')

hello()

输出结果如下:

1
Hello World!

下面定义一个带有参数的函数,如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 定义计算面积函数
def area(width, height):
    return width * height


def print_hello(name):
    print('Hello', name)


print_hello('Carol')
w = 1
h = 2
print('width =', w, 'height =', h, 'area =', area(w, h))

输出结果如下:

1
2
Hello Carol
width = 1 height = 2 area = 2

2.函数调用

在调用函数的时候,通常需要知道函数的名称和参数。如果传入的参数数量不对,或者传入的参数数量对了而参数类型不能被函数接受,则程序就会报错,如下所示:

1
2
3
4
5
6
# 定义函数
def print_me(str):
    print(str)

# 调用函数
print_me('Hello World!')

输出结果如下:

1
Hello World!

3.参数传递

3.1可变对象与不可变对象

在 Python 中,可变对象与不可变对象如下所示:

  • 可变对象:List(列表)、Set(集合)、Dictionary(字典)
  • 不可变对象:Number(数字)、String(字符串)、Tuple(元组)

(1)对于不可变对象,如变量赋值a = 1后再赋值a = 2,这里实际是新生成了一个int对象2,再让a指向它,而1被丢弃,不是改变a的值,而是新生成了a

(2)对于可变对象,如变量赋值list = [1, 2, 3, 4,]后再赋值list[2] = 5,相当于更改了列表list的第三个元素的值,而列表list本身没有动,只是其内部的一部分值被修改了。 在Python参数传递的时候,对于可变对象与不可变对象有如下说明:

此外:

(1)对于不可变对象,如fun(a),传递的只是a的值,没有影响a对象本身。比如在fun(a)内部修改a的值,只是修改另一个复制的对象,不会影响a本身。

(2)对于可变对象,如fun(list),则是将list真正的传过去,修改后fun外部的list也会受影响。

Python中一切都是对象,严格意义我们不能说值传递还是引用传递,我们应该说传不可变对象和传可变对象。

3.2不可变对象的传递

下面给出对不可变对象进行传递,如下所示:

1
2
3
4
5
6
def change_int(a):
    a = 10

b = 2
change_int(b)
print(b)  # 输出结果为2

实例中有int对象2,指向它的变量是b,在传递给change_int函数时,按传值的方式复制了变量bab都指向了同一个int对象,在 a=10时,则新生成一个int值对象10,并让a指向它。

3.3可变对象的传递

可变对象在函数里修改了参数,那么在调用这个函数的函数里,原始的参数也被改变了,如下所示:

1
2
3
4
5
6
7
8
def change_me(mylist):
    mylist.append([1, 2, 3, 4])
    print('函数内取值:', mylist)
    return

mylist = [10, 20, 30]
change_me(mylist)
print('函数外取值:', mylist)

输出结果如下:

1
2
函数内取值 [10, 20, 30, [1, 2, 3, 4]]
函数外取值 [10, 20, 30, [1, 2, 3, 4]]

可以看出,传入函数的对象和在末尾添加新内容的对象用的是同一个引用。

4.参数类型

以下是调用函数时可使用的参数类型:

  • 必需参数
  • 关键字参数
  • 默认参数
  • 不定长参数

4.1必需参数

必需参数要用正确的顺序传入函数,调用时的数量必须和声明时的一样,如下所示:

1
2
3
4
5
6
# 定义函数
def print_me(str):
    print(str)

# 调用函数
print_me()  # 这里没有给参数,所以程序报错

4.2关键字参数

关键字参数和函数调用关系紧密,函数调用使用关键字参数来确定传入的参数值。使用关键字参数允许函数调用时参数的顺序与声明时不一致,因为 Python 解释器能够用参数名匹配参数值。

下面使用参数名来进行函数调用,如下所示:

1
2
3
4
5
6
# 定义函数
def print_me(str):
    print(str)

# 调用函数
print_me(str='Hello World!')

输出结果如下:

1
Hello World!

在进行函数调用的时候也可以不需要指定参数顺序,如下所示:

1
2
3
4
5
6
7
8
# 定义函数
def print_info(name, age):
    print('名字:', name)
    print('年龄:', age)
    return

# 调用函数
print_info(age=20, name='Carol')

输出结果如下:

1
2
名字: Carol
年龄: 20

4.3 默认参数

调用函数时,如果没有传递参数,则会使用默认参数。

注意:默认参数必须放在最后,否则会报错。

以下实例中如果没有传入age参数,则使用默认值,如下所示:

1
2
3
4
5
6
7
8
def print_info(name, age=30):
    print('名字:', name)
    print('年龄:', age)
    return

print_info(age=50, name='Jack')
print('============')
print_info(name='Carol')

输出结果如下:

1
2
3
4
5
名字: Jack
年龄: 50
============
名字: Carol
年龄: 30

4.4不定长参数

你可能需要一个函数能处理比当初声明时更多的参数。这些参数叫做不定长参数,和上述两种参数不同,声明时不会命名。基本语法如下所示:

1
2
3
4
def functionname([formal_args,] *var_args_tuple):
   "函数_文档字符串"
   function_suite
   return [expression]

加了星号*的参数会以元组(Tuple)的形式导入,存放所有未命名的变量参数。如下所示:

1
2
3
4
5
6
def print_info(arg1, *vartuple):
    print('输出:')
    print(arg1)
    print(vartuple)

print_info('hello', 10, 20, 30, 'world')

输出结果如下:

1
2
3
输出:
hello
(10, 20, 30, 'world')

如果在函数调用时没有指定参数,它就是一个空元组。我们也可以不向函数传递未命名的变量,如下所示:

1
2
3
4
5
6
7
8
9
def print_info(arg1, *vartuple):
    print('输出:')
    print(arg1)
    for i in vartuple:
        print(i)
    return

print_info(10)
print_info('hello', 10, 20, 30, 'world')

输出结果如下:

1
2
3
4
5
6
7
8
输出:
10
输出:
hello
10
20
30
world

另外一种带有两个星号**的参数会以字典的形式导入,基本语法如下所示:

1
2
3
4
def functionname([formal_args,] **var_args_dict):
   "函数_文档字符串"
   function_suite
   return [expression]

示例如下所示:

1
2
3
4
5
6
def print_info(arg1, **vardict):
    print('输出:')
    print(arg1)
    print(vardict)

print_info(1, a=2, b=3)

输出结果如下:

1
2
3
输出:
1
{'a': 2, 'b': 3}

在声明函数时,参数中星号*可以单独出现,如下所示:

1
2
def f(a, b, *, c):
    return a + b + c

但是如果单独出现星号*,则它后面的参数必须用关键字传入,如下所示:

1
2
3
4
5
def f(a, b, *, c):
    return a + b + c

print(f(1, 2, 3))  # 程序会报错
print(f(1, 2, c=3))  # 输出为6

5.匿名函数

Python使用lambda来创建匿名函数,不再使用def语句定义一个函数。

使用lambda时应注意以下几点:

  • lambda只是一个表达式,函数体比def简单很多。
  • lambda的主体是一个表达式,而不是一个代码块。仅仅能在lambda表达式中封装有限的逻辑进去。
  • lambda函数拥有自己的命名空间,且不能访问自己参数列表之外或全局命名空间里的参数。
  • 虽然lambda函数看起来只能写一行,却不等同于CC++的内联函数,后者的目的是调用小函数时不占用栈内存从而增加运行效率。

lambda函数的语法只包含一个语句,具体格式如下所示:

1
lambda [arg1 [,arg2,.....argn]]:expression

一个简单的lambda实例如下所示:

1
2
sum = lambda arg1, arg2: arg1 + arg2
print('相加后的值为:', sum(10, 20))

输出结果如下:

1
相加后的值为: 30

当然,lambda匿名函数也是可以使用关键字参数进行参数传递,如下所示:

1
2
sum = lambda x, y: x ** 2 + y ** 2
print(sum(y=2, x=3))  # 输出结果为13

同样的,lambda匿名函数也是可以设定默认值,如下所示:

1
2
3
4
sum = lambda x=0, y=0: x ** 1 + y ** 2
print(sum(y=2, x=3))
print(sum(3))  # 这里x=3,y=0
print(sum(y=2))  # 这里x=0,y=2

输出结果如下:

1
2
3
7
3
4

所以,如果只打算给其中一部分参数设定默认值,那么应当将其放在靠后的位置(和定义函数时一样,避免歧义),否则会报错。

6.return 语句

return [表达式]语句用于退出函数,选择性地向调用方返回一个表达式。不带参数值的return语句返回None

下面给出return语句的用法,如下所示:

1
2
3
4
5
6
7
def sum(arg1, arg2):
    total = arg1 + arg2
    print('函数内:', total)
    return total

total = sum(10, 20)
print('函数外:', total)

输出结果如下:

1
2
函数内: 30
函数外: 30

7.变量作用域

Python 中的变量并不是在哪个位置都可以访问的,访问权限决定于这个变量是在哪里赋值的。变量的作用域决定了在哪一部分程序可以访问哪个特定的变量名称。

Python 的作用域一共有4种,分别是:

  • L(Local)局部作用域
  • E(Enclosing)闭包函数外的函数中
  • G(Global)全局作用域
  • B(Built-in)内建作用域

L–>E–>G–>B的规则查找,即:在局部找不到,便会去局部外的局部找(例如闭包),再找不到就会去全局找,再者去内建中找。作用范围示例如下所示:

1
2
3
4
5
6
7
x = int(2.9)  # 内建作用域
g_count = 0  # 全局作用域

def outer():
    o_count = 1  # 闭包函数外的函数中
    def inner():
        i_count = 2  # 局部作用域

Python 中只有模块(module),类(class)以及函数(def、lambda)才会引入新的作用域,其它的代码块(如 if/elif/else/、try/except、for/while等)是不会引入新的作用域的,也就是说这些语句内定义的变量,外部也可以访问,如下所示:

1
2
3
4
5
if True:
    msg = 'HelloWorld'
    print(msg)

print(msg)

输出结果如下:

1
2
HelloWorld
HelloWorld

可以看出,实例中msg变量定义在if语句块中,但外部还是可以访问的。下面将msg定义在函数内,则它就是局部变量,外部不能访问,如下所示:

1
2
3
4
5
def test():
    msg_inner = 'HelloWorld'
    print(msg_inner)

print(msg_inner)  # 这里程序会报错,显示name 'msg_inner' is not defined,也就是msg_inner未定义

7.1全局变量和局部变量

定义在函数内部的变量拥有一个局部作用域,定义在函数外的拥有全局作用域。局部变量只能在其被声明的函数内部访问,而全局变量可以在整个程序范围内访问。调用函数时,所有在函数内声明的变量名称都将被加入到作用域中,如下所示:

1
2
3
4
5
6
7
8
9
total = 0  # 定义全局变量

def sum(arg1, arg2):
    total = arg1 + arg2  # 这里的total是局部变量
    print('函数内是局部变量:', total)
    return total

sum(10, 20)
print('函数外是全局变量:', total)

输出结果如下:

1
2
函数内是局部变量: 30
函数外是全局变量: 0

7.2global 和 nonlocal 关键字

当内部作用域想修改外部作用域的变量时,就要用到globalnonlocal关键字了。如果想为一个在函数外的变量重新赋值,并且这个变量会作用于许多函数中时,就需要告诉Python这个变量的作用域是全局变量。此时用global语句就可以变成这个任务,如下所示:

1
2
3
4
5
6
7
8
9
num = 1

def fun():
    global num  # 加了global后,在函数内部是可以改变外面的全局变量
    num = 123
    print('在函数中的 num:', num)  # 这里输出内部的num=123

fun()
print('全局变量 num:', num)

输出结果如下:

1
2
在函数中的 num: 123
全局变量 num: 123

如果要修改嵌套作用域(enclosing 作用域,外层非全局作用域)中的变量,则需要nonlocal关键字了。也就是说,nonlocal关键字表示否定当前命名空间的作用域,寻找父函数的作用域并绑定对象;使用nonlocal关键字可以在一个嵌套的函数中修改嵌套作用域中的变量。

我们先来看一个例子,如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def outer():
    count = 1

    def inner():
        count = 12

    inner()
    print(count)

outer()  # 输出结果为1

上面的程序中,在嵌套的inner()函数内,count = 12会创建一个新的变量,但是我们想要使用count = 12,也就是想要输出结果为12,那么就需要用到nonlocal关键字了,如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def outer():
    count = 1

    def inner():
        nonlocal count
        count = 12

    inner()
    print(count)

outer()  # 输出结果为12

还有一种情况,如下所示:

1
2
3
4
5
6
a = 10
def test():
    a = a + 1
    print(a)

test()  # 程序会出现局部作用域引用错误

之所以出现局部作用域引用错误,是在因为test()函数中,a使用的是局部变量,未定义,无法修改。但我们可以通过修改a为局部变量,通过函数参数传递即可是程序正常运行,如下所示:

1
2
3
4
5
6
a = 10
def test(a):
    a = a + 1
    print(a)

test(a)  # 输出结果为11