Python 函数

1. 函数介绍

2. 局部/全局变量

3. 函数的参数

3.1 参数顺序

3.2 默认参数

3.3 不定长参数

3.4 引用参数

4. 递归函数

5. 匿名函数

 

 

1. 函数介绍

什么是函数?

函数是组织好的,可重复使用的,用来实现特定功能的代码段。

函数能提高应用的模块性和代码的重复利用率,以提高编写效率。Python 提供了许多内建函数,比如 print()。我们也可以自己创建函数,这被叫做用户自定义函数。

自定义函数

语法:

# 定义函数
def 函数名(形参1, 形参2, ...):
    # 引号中可添加函数的文档说明
    """
    :param 形参1:
    :param 形参2:
    :return:
    """
    函数体(即需要实现特定功能的代码块)
    return 返回值
    

# 调用函数
函数名(实参1, 实参2, ...)
  • 定义时小括号中的参数,用来接收参数用的,称为 “形参”;调用时小括号中的参数,用来传递给函数用的,称为 “实参”。
  • 函数可以根据有没有参数,有没有返回值来自由组合。

示例:

 1 >>> def cal(a, b):
 2 ...     result1 = a + b
 3 ...     result2 = a - b
 4 ...     return result1, result2  # 多个返回值,利用了元组
 5 ...
 6 >>> cal(1, 2)
 7 (3, -1)
 8 >>> result = cal(3, 4)  # 保存函数的返回值
 9 >>> print(result)
10 (7, -1)

 

2. 局部/全局变量

局部变量

  • 局部变量,就是在函数内部定义的变量。
  • 局部变量的作用,是为了临时保存数据而需要在函数中定义变量来进行存储,这就是它的作用。
  • 不同的函数,可以定义相同的名字的局部变量,各用各的不会产生影响。

全局变量

  • 在函数外边定义的变量叫做全局变量,全局变量能够在所有的函数中进行访问。
  • 如果全局变量名和局部变量名相同,那么将使用局部变量。
  • 全局变量的位置必须在函数调用之前,但可在函数定义之后。
  • 如果在函数中修改全局变量,那么就需要使用 global 进行声明,否则会报错。

示例:修改全局变量

 1 # 定义全局变量
 2 num = 100
 3 
 4 def test1():
 5     global num  # 声明全局变量
 6     num = 200  # 修改全局变量
 7     print(num)
 8     
 9 def test2():
10     print(num)
11     
12 test1()  # 200
13 test2()  # 200

可变类型的全局变量

  • 不可变类型的全局变量,不使用 global 则无法修改全局变量;
  • 可变类型的全局变量,不使用 global 也可修改全局变量。
>>> # 当全局变量为不可变类型时
>>> a = 1
>>> def f1():
...     a += 1  # 未声明全局变量则会报错
...     print(a)
...
>>> f1()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in f1
UnboundLocalError: local variable 'a' referenced before assignment
>>>
>>> # 当全局变量为可变类型时
>>> li = [1]
>>> def f2():
...     li.append(2)  # 未声明全局变量也可直接引用
...     print(li)
...
>>> f2()
[1, 2]

 

3. 函数的参数

3.1 参数顺序

 1 >>> def test(a, b):
 2 ...     print(a, b)
 3 ...
 4 >>> test(1, 2)  # 默认按传入的顺序
 5 1 2
 6 >>> test(b=2, a=1)  # 指定关键字来传参
 7 1 2
 8 >>> test(b=2, 1)  # 指定关键字需要放在默认传参之后,否则会报错
 9   File "<stdin>", line 1
10 SyntaxError: positional argument follows keyword argument
11 >>> test(1, b=2)
12 1 2

  

3.2 默认参数

  • 调用函数时,若默认参数的值没有传入,则会使用默认值。
  • 默认参数需要放在形参列表的最后面,否则会报错。
 1 >>> def test(name, age=18):
 2 ...     print(name, age)
 3 ...
 4 >>> test("xiaoming")
 5 xiaoming 18
 6 >>> test("xiaoming", 19)
 7 xiaoming 19
 8 >>>
 9 >>> def test(age=18, name):
10 ...     print(name, age)
11 ...
12   File "<stdin>", line 1
13 SyntaxError: non-default argument follows default argument

注意:应尽量避免将默认参数定义为可变类型,否则容易出现程序逻辑问题

1)问题示例

在下面的例子中,函数 buggy() 在每次调用时,添加参数 arg 到一个空的列表 result ,然后打印输出一个单值列表。

但是存在一个问题:只有在第一次调用时列表是空的,第二次调用时就会存在之前调用的返回值。

1 def buggy(arg, result=[]):
2     result.append(arg)
3     print(result)
4 
5 print('--------1--------')
6 buggy('a')
7 print('--------2--------')
8 buggy('b')  # 期望得到 ['b']

执行结果:

--------1--------
['a']
--------2--------
['a', 'b']

2)问题解释

  1. 默认参数值在函数被定义时已经计算出来,而不是在程序运行时。
  2. 只要函数调用时没有传递新的列表来覆盖默认参数列表,函数就会使用定义时的那个列表,并且操作依次叠加。
  3. 上面两次调用中,都没有传递新的列表,程序会调用定义函数时保存的默认参数,并在上一次的基础上进行操作叠加,即列表在 append 的时候会在 result 原来的基础上 append 追加值,所以会产生以上结果。

我们通过打印列表的 id 进行辨识来看看:

1 def buggy(arg, result=[]):
2     print(id(result))
3     result.append(arg)
4     print(result)
5 
6 print('--------1--------')
7 buggy('a')
8 print('--------2--------')
9 buggy('b')  # expect ['b']

执行结果:

--------1--------
12205768
['a']
--------2--------
12205768
['a', 'b']

我们会发现 id 值是相同的,说明两次执行时使用的都是开始定义函数时的默认参数,进行了操作叠加。

下面我们传递新的列表看看

示例 1:

1 def buggy(arg, result=[]):
2     print(id(result))
3     result.append(arg)
4     print(result)
5 
6 print('--------1--------')
7 buggy('a')
8 print('--------2--------')
9 buggy('b', [])  # 传递了新的列表

执行结果:

--------1--------
18497224
['a']
--------2--------
18504648
['b']

发现,列表 id 不同,并且得到了我们期望的值。

示例 2:

 1 def add_end(L=[]):
 2     print(id(L)) 
 3     L.append('END')
 4     return L
 5 
 6 # 未传递列表,会对默认列表对象进行累加
 7 print(add_end())
 8 print(add_end())
 9 print("*"*30)
10 # 传递有变量引用的列表(引用传递),会对新列表对象进行累加
11 li = [1, 2, 3]
12 print(add_end())
13 print(add_end(li))
14 print(add_end(li))
15 print("*"*30)
16 # 传递没有变量引用的列表(值传递),每次返回的都是新列表
17 print(add_end([1, 2, 3]))
18 print(add_end(["x", "y", "z"]))

执行结果:

2385843760264
['END']
2385843760264
['END', 'END']
******************************
2385843760264
['END', 'END', 'END']
2385843760584
[1, 2, 3, 'END']
2385843760584
[1, 2, 3, 'END', 'END']
******************************
2385843760520
[1, 2, 3, 'END']
2385843760520
['x', 'y', 'z', 'END']

注意,在上述例子中,传递没有变量引用的列表时两次调用函数的 id(L) 结果一致,是因为 Python 的内存地址分配特性:对于没有变量引用的相同数据类型的对象,对其所分配的内存地址都是相同的。

 1 >>> id([1, 2, 3])
 2 1619966208264
 3 >>> id(["z", "y", "z"])
 4 1619966208264
 5 >>> 
 6 >>> id((1, 2 ,3))
 7 1619966178000
 8 >>> id(("x", "y", "z"))
 9 1619966178000
10 >>> 
11 >>> id(1000)
12 1619963916016
13 >>> id(2000)
14 1619963916016

3)解决方法

方法1:

1 def works(arg):
2     result = []
3     result.append(arg)
4     print(result)
5 
6 works('a')
7 works('b')

执行结果:

['a']
['b']

方法2:

1 def nonbuggy(arg, result=None):
2     if result is None:
3         result = []
4     result.append(arg)
5     print(result)
6 
7 nonbuggy('a')
8 nonbuggy('b')

执行结果:

['a']
['b']

  

3.3 不定长参数

当需要一个函数能处理比当初声明时更多的参数时,这些参数就叫做不定长参数,声明时无需命名。

def 函数名(普通参数, *args, **kwargs):
    函数体
  • 加了 * 的变量 args 会存放所有未命名的变量参数,args 为元组。
  • 加了 ** 的变量 kwargs 会存放命名参数,即形如 key=value 的参数,kwargs 为字典。
  • 同时使用 *args 和 **kwargs 时,*args 参数必须要在 **kwargs 前,否则会报错。
 1 >>> def func(a, b, *args, **kwargs):
 2 ...     print(a)
 3 ...     print(b)
 4 ...     print(args)
 5 ...     print(kwargs)
 6 ...
 7 
 8 # 直接传入元组和字典时,都传给了args
 9 >>> func(1, 2, 3, 4, m=5, n=6)  
10 1
11 2
12 (3, 4)
13 {'m': 5, 'n': 6}
14 
15 # 直接传入元组和字典时,都传给了args
16 >>> c = (3, 4, 5)
17 >>> d = {"m":6, "n":7}
18 >>> func(1, 2, c, d)  
19 1
20 2
21 ((3, 4, 5), {'m': 6, 'n': 7})
22 {}
23 
24 # 注意加了星号时为“拆包”:将传入的元组和字典分别对应上args和kwargs
25 # 解包时的传入顺序也需*在前,**在后
26 >>> func(1, 2, *c, **d)  
27 1
28 2
29 (3, 4, 5)
30 {'m': 6, 'n': 7}
31 
32 
33 # 关键字需是字母
34 >>> func(1, 2, a=1, 1=1)
35   File "<stdin>", line 1
36 SyntaxError: keyword can't be an expression
37 # 解包的键也需是字符串
38 >>> d = {"a": 1, 2: 1}
39 >>> func(1, 2, **d)
40 Traceback (most recent call last):
41   File "<stdin>", line 1, in <module>
42 TypeError: func() keywords must be strings

 

3.4 引用参数

当可变类型与不可变类型的变量分别作为函数参数时,会有什么不同吗?

  • Python中函数参数是引用传递(注意不是值传递),即形参指向了实参的地址。
  • 对于不可变类型,因为变量不能修改,所以运算不会影响到变量自身;而对于可变类型来说,函数体中的运算有可能会更改传入的参数变量。
 1 >>> # 自定义函数
 2 >>> def selfAdd(a):
 3 ...     "自增"
 4 ...     a += a
 5 ...
 6 >>> # 传入不可变类型的参数时
 7 >>> a_int = 1
 8 >>> selfAdd(a_int)
 9 >>> a_int
10 1
11 >>> # 传入可变类型的参数时
12 >>> a_list = [1, 2]
13 >>> selfAdd(a_list)
14 >>> a_list
15 [1, 2, 1, 2]

  

4. 递归函数

如果一个函数在内部调用了自身的话,这个函数就是递归函数(recursive function)。

以阶乘为例子,首先观察其规律:

1! = 1
2! = 2 × 1 = 2 × 1!
3! = 3 × 2 × 1 = 3 × 2!
4! = 4 × 3 × 2 × 1 = 4 × 3!

...
n! = n × (n-1)!

实现:

1 def factorial(num):
2     if num > 1:
3         return num * factorial(num-1)
4     else:
5         return 1
6         
7 print(factorial(3))  # 结果为6

 

5. 匿名函数

用 lambda 关键词能创建小型匿名函数,这种函数得名于省略了用def声明函数的标准步骤。

 lambda 函数的语法只包含一个语句,如下:

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

 Lambda 函数能接收任何数量的参数,但只能返回一个表达式的值。

示例:

1 >>> sum = lambda a, b: a + b
2 >>> print(sum(2, 3))
3 5

匿名函数的应用场景

1. 匿名函数作为函数参数进行传递

1 >>> def sum(a, b, opt):
2 ...     print(a, b)
3 ...     print("result=", opt(a, b))
4 ...
5 >>> sum(2, 3, lambda a,b: a+b)
6 2 3
7 result= 5

2. 列表中字典(或多维列表、元组)的排序规则

1 >>> stus = [ {"name": "zhangsan", "age": 18}, {"name": "lisi", "age": 19}, {"name": "wangwu", "age": 17} ]
2 >>>
3 >>> stus.sort(key=lambda item:item["age"])  # 按 age 排序
4 >>> stus
5 [{'name': 'wangwu', 'age': 17}, {'name': 'zhangsan', 'age': 18}, {'name': 'lisi', 'age': 19}]
6 >>> stus.sort(key=lambda item:item["name"])  # 按 name 排序
7 >>> stus
8 [{'name': 'lisi', 'age': 19}, {'name': 'wangwu', 'age': 17}, {'name': 'zhangsan', 'age': 18}]

 

posted @ 2020-02-17 21:17  Juno3550  阅读(168)  评论(0编辑  收藏  举报