1、定义函数

1.1 格式

定义函数的格式如下:

  1. def 函数名():
  2. 函数封装的代码
  3. ……
  1. def 是英文 define 的缩写
  2. 函数名称 应该能够表达 函数封装代码 的功能,方便后续的调用
  3. 函数名称 的命名应该 符合 标识符的命名规则
    • 可以由 字母下划线数字 组成
    • 不能以数字开头
    • 不能与关键字重名

demo:

  1. # 定义一个函数,能够完成打印信息的功能
  2. def printInfo():
  3. print '------------------------------------'
  4. print ' 语雀,一只能言善语的云雀'
  5. print '------------------------------------'

如果想定义一个什么事也不做的空函数,可以用pass语句:

  1. def nop():
  2. pass

pass语句什么都不做,那有什么用?实际上pass可以用来作为占位符,
比如现在还没想好怎么写函数的代码,就可以先放一个pass,让代码能运行起来。

1.2 函数调用

调用函数很简单的,通过 函数名() 即可完成调用
demo:

  1. # 定义完函数后,函数是不会自动执行的,需要调用它才可以
  2. printInfo()

1.3 文档说明

  • 在开发中,如果希望给函数添加注释,应该在 定义函数 的下方,使用 连续的三对引号
  • 连续的三对引号 之间编写对函数的说明文字
  • 函数调用 位置,使用快捷键 CTRL + Q 可以查看函数的说明信息

image.png

注意:因为 函数体相对比较独立函数定义的上方,应该和其他代码(包括注释)保留 两个空行

  1. def test(a,b):
  2. """用来完成对2个数求和"""
  3. print("%d" % (a + b))
  4. test(11,22)

如果执行,以下代码
help(test)
能够看到test函数的相关说明

  1. Help on function test in module __main__:
  2. test(a, b)
  3. 用来完成对2个数求和

1.4 PyCharm 技巧

  • F8 Step Over 可以单步执行代码,会把函数调用看作是一行代码直接执行
  • F7 Step Into 可以单步执行代码,如果是函数,会进入函数内部

2、函数参数

2.1 作用

  • 函数,把 具有独立功能的代码块 组织为一个小模块,在需要的时候 调用
  • 函数的参数,增加函数的 通用性,针对 相同的数据处理逻辑,能够 适应更多的数据
    1. 在函数 内部,把参数当做 变量 使用,进行需要的数据处理
    2. 函数调用时,按照函数定义的参数顺序,把 希望在函数内部处理的数据通过参数 传递

image.png

2.2 形参和实参

  • 形参定义 函数时,小括号中的参数,是用来接收参数用的,在函数内部 作为变量使用
  • 实参调用 函数时,小括号中的参数,是用来把数据传递到 函数内部 用的

image.png

2.3 可变参数

有时可能需要一个函数能处理比当初声明时更多的参数。
这些参数叫做不定长参数,声明时不会命名。
基本语法如下:

  1. def functionname(*args,**kwargs):
  2. pass
  3. # args和kwargs的名字可以任意写,不过建议写成这样通用的写法

加了星号 *`` ``的参数叫可变参数会以元组(tuple)的形式导入,存放所有未命名的变量参数。
加了两个**的参数叫关键字参数,以字典的方式保存,需写key名。

  1. In [2]: def test(a,b,c=33,*args, **kwargs):
  2. ...: print(a)
  3. ...: print(b)
  4. ...: print(c)
  5. ...: print(args)
  6. ...: print(kwargs)
  7. ...:
  8. In [3]: test(11,22,33,44,55,task=66,done=77)
  9. 11 # a
  10. 22 # b
  11. 33 # 默认参数c
  12. (44, 55) # 分配完其他的,剩下的所有给args,元组格式
  13. {'task': 66, 'done': 77} # 写了key的,给kwargs,字典格式

2.4 关键字参数

对于关键字参数,函数的调用者可以传入任意不受限制的关键字参数。
至于到底传入了哪些,就需要在函数内部通过kw检查。

person()函数为例,我们希望检查是否有cityjob参数:

  1. def person(name, age, **kw):
  2. if 'city' in kw:
  3. # 有city参数
  4. pass
  5. if 'job' in kw:
  6. # 有job参数
  7. pass
  8. print('name:', name, 'age:', age, 'other:', kw)
  9. # 但是调用者仍可以传入不受限制的关键字参数:
  10. person('语雀', 18, city='杭州', addr='Chaoyang', zipcode=123456)
  11. name: 语雀 age: 18 other: {'city': '杭州', 'addr': 'Chaoyang', 'zipcode': 123456}

如果要限制关键字参数的名字,就可以用命名关键字参数,例如,
只接收cityjob作为关键字参数。这种方式定义的函数如下:

  1. def person(name, age, *, city, job):
  2. print(name, age, city, job)

和关键字参数**kw不同,命名关键字参数需要一个特殊分隔符*
*后面的参数被视为命名关键字参数。
调用方式如下:

  1. >>> person('语雀', 18, city='杭州', job='Engineer')
  2. 语雀 18 杭州 Engineer

如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了:

  1. def person(name, age, *args, city, job):
  2. print(name, age, args, city, job)

命名关键字参数必须传入参数名,这和位置参数不同。如果没有传入参数名,调用将报错:

  1. >>> person('语雀', 18, city='杭州', job='Engineer')
  2. Traceback (most recent call last):
  3. File "<stdin>", line 1, in <module>
  4. TypeError: person() takes 2 positional arguments but 4 were given
  5. 语雀 18 () 杭州 Engineer

由于调用时缺少参数名cityjob,Python解释器把这4个参数均视为位置参数,
person()函数仅接受2个位置参数。

命名关键字参数可以有缺省值,从而简化调用:

  1. def person(name, age, *, city='杭州', job):
  2. print(name, age, city, job)


由于命名关键字参数city具有默认值,调用时,可不传入city参数:

  1. >>> person('语雀', 18, job='Engineer')
  2. 语雀 18 杭州 Engineer

使用命名关键字参数时,要特别注意,如果没有可变参数,就必须加一个*作为特殊分隔符。如果缺少*,Python解释器将无法识别位置参数和命名关键字参数:

  1. def person(name, age, city, job):
  2. # 缺少 *,city和job被视为位置参数
  3. pass

2.5 返回值

所谓“返回值”,就是程序中函数完成一件事情后,最后给调用者的结果。
return 关键字用于退出函数,选择性地向调用方返回一个表达式

  1. In [26]: def sum( arg1, arg2 ):
  2. ...: # 返回2个参数的和."
  3. ...: total = arg1 + arg2
  4. ...: print ("函数内 : ", total)
  5. ...: return total
  6. ...: # return用来结束函数,后面跟的变量返回给调用者
  7. In [27]: total = sum( 10, 20 ) #有返回值,则需要一个变量来接收保存
  8. ('函数内 : ', 30)
  9. In [28]: print ("函数外 : ", total)
  10. ('函数外 : ', 30)

函数根据有没有参数,有没有返回值可以相互组合成
四种类型的函数:

有返回值 无返回值
有参数 有参,有返回函数 有参,无返回函数
无参数 无参,有返回函数 无参,无返回函数

3、变量作用域

Python 中,程序的变量并不是在哪个位置都可以访问的,访问权限决定于这个变量是在哪里赋值的。

变量的作用域决定了在哪一部分程序可以访问哪个特定的变量名称。Python的作用域一共有4种,分别是:

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

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

  1. x = int(2.9) # 内建作用域
  2. g_count = 0 # 全局作用域
  3. def outer():
  4. o_count = 1 # 闭包函数外的函数中
  5. def inner():
  6. i_count = 2 # 局部作用域

3.1 全局变量和局部变量

定义在函数内部的变量拥有一个局部作用域,定义在函数外的拥有全局作用域。

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

  1. total = 0 # 这是一个全局变量
  2. # 可写函数说明
  3. def sum( arg1, arg2 ):
  4. #返回2个参数的和."
  5. total = arg1 + arg2 # total在这里是局部变量.
  6. print ("函数内是局部变量 : ", total)
  7. return total
  8. #调用sum函数
  9. sum( 10, 20 )
  10. print ("函数外是全局变量 : ", total)

以上实例输出结果:

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

3.2 global 和 nonlocal关键字

当内部作用域想修改外部作用域的变量时,就要用到global和nonlocal关键字了。

以下实例修改全局变量 num:

  1. num = 1
  2. def fun1():
  3. global num # 需要使用 global 关键字声明
  4. print(num)
  5. num = 123
  6. print(num)
  7. fun1()
  8. print(num)

以上实例输出结果:

  1. 1
  2. 123
  3. 123

如果要修改嵌套作用域(enclosing 作用域,外层非全局作用域)中的变量则需要 nonlocal 关键字了,如下实例:

  1. def outer():
  2. num = 10
  3. def inner():
  4. nonlocal num # nonlocal关键字声明
  5. num = 100
  6. print(num)
  7. inner()
  8. print(num)
  9. outer()

以上实例输出结果:

  1. 100
  2. 100

另外有一种特殊情况,假设下面这段代码被运行:

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

以上程序执行,报错信息如下:

  1. Traceback (most recent call last):
  2. File "test.py", line 7, in <module>
  3. test()
  4. File "test.py", line 5, in test
  5. a = a + 1
  6. UnboundLocalError: local variable 'a' referenced before assignment

错误信息为局部作用域引用错误,因为 test 函数中的 a 使用的是局部,未定义,无法修改。

修改 a 为全局变量,通过函数参数传递,可以正常执行输出结果为:

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

执行输出结果为:11

4、递归函数

在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。

举个例子,我们来计算阶乘 n! = 1 * 2 * 3 * ... * n

  • 解决办法1:

函数基础 - 图4

看阶乘的规律

  1. 1! = 1
  2. 2! = 2 × 1 = 2 × 1!
  3. 3! = 3 × 2 × 1 = 3 × 2!
  4. 4! = 4 × 3 × 2 × 1 = 4 × 3!
  5. ...
  6. n! = n × (n-1)!
  • 解决办法2:

函数基础 - 图5
原理
函数基础 - 图6

理论上,所有的递归函数都可以写成循环的方式,但循环的逻辑不如递归清晰。

使用递归时必须有一个明确的结束条件,不然会导致无限调用。

使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出
把上面的递归函数的参数改成100000就导致栈溢出!
函数基础 - 图7
解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。

5、匿名函数

匿名函数(lambda)是指没有名字的函数,应用在需要一个函数但是又不想费神去命名这个函数的场合。通常情况下,这样的函数只使用一次
lambda函数的语法只包含一个语句:
lambda x: x * x
实际上就是:

  1. def f(x):
  2. return x * x

关键字lambda表示匿名函数,冒号前面的x表示函数参数。
匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果

用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数:

  1. In [4]: f = lambda x: x * x
  2. In [5]: f
  3. Out[5]: <function __main__.<lambda>>
  4. In [6]: f(5)
  5. Out[6]: 25

同样,也可以把匿名函数作为返回值返回,比如:

  1. In [14]: def build(x, y):
  2. ...: return lambda: x * x + y * y
  3. ...:
  4. In [15]: f = build(10,20) # 用f来接收build返回的函数地址
  5. In [16]: f() #调用函数方法
  6. Out[16]: 500

6、内置函数

6.1 map()

描述
map() 会根据提供的函数对指定序列做映射
第一个参数 function 以参数序列中的每一个元素调用 function 函数,返回包含每次 function 函数返回值的新列表。
语法
map() 函数语法:
map(function, iterable, ...)
参数

  1. function -- 函数,有两个参数
  2. iterable -- 一个或多个序列

返回值
Python 2.x 返回列表。
Python 3.x 返回迭代器。
实例
函数基础 - 图8

6.2 filter()

描述
filter() 函数用于过滤序列,过滤掉不符合条件的元素,返回由符合条件元素组成的新列表。

该接收两个参数,第一个为函数,第二个为序列,序列的每个元素作为参数传递给函数进行判,然后返回 True 或 False,最后将返回 True 的元素放到新列表中。
根据返回值是True还是False决定保留还是丢弃该元素
语法
以下是 filter() 方法的语法:
filter(function, iterable)

参数

  1. function -- 判断函数。
  2. iterable -- 可迭代对象。

返回值
Python 2.x 返回列表。
Python 3.x 返回迭代器。
实例
函数基础 - 图9

6.3 reduce()

描述
reduce() 函数会对参数序列中元素进行累积。

函数将一个数据集合(链表,元组等)中的所有数据进行下列操作:用传给 reduce 中的函数 function(有两个参数)先对集合中的第 1、2 个元素进行操作,得到的结果再与第三个数据用 function 函数运算,最后得到一个结果。

语法
reduce() 函数语法:
reduce(function, iterable[, initializer])

参数

  1. function -- 函数,有两个参数
  2. iterable -- 可迭代对象
  3. initializer -- 可选,初始参数

返回值
返回函数计算结果。
实例:
函数基础 - 图10

6.4 zip()

描述
将多个可迭代对象一一对应(不符合则不保留)重新合并成一个zip对象,可迭代的方式
就像拉链,一一对应,如果单独一个则不保留
语法
zip 语法:

  1. zip([iterable, ...])

参数:

  1. iterabl -- 一个或多个迭代器;

返回值
返回一个对象。
实例
函数基础 - 图11

6.5 eval()

描述
eval() 函数用来执行一个字符串表达式,并返回表达式的值。
语法
以下是 eval() 方法的语法:

  1. eval(expression[, globals[, locals]])

参数

  1. expression -- 表达式。
  2. globals -- 变量作用域,全局命名空间,如果被提供,则必须是一个字典对象。
  3. locals -- 变量作用域,局部命名空间,如果被提供,可以是任何映射对象。

返回值
返回表达式计算结果。
实例
函数基础 - 图12

  1. x = 2
  2. y = 1
  3. num1 = eval('x+y')
  4. print(num1)
  5. def hc():
  6. x = 2
  7. y = 2
  8. num2 = eval('x+y')
  9. print(num2)
  10. num3 = eval('x+y',globals())
  11. print(num3)
  12. num4 = eval('x+y', globals(),locals())
  13. print(num4)
  14. hc()
  15. 3
  16. 4
  17. 3
  18. 4