函数是可重用的代码块,它不仅可以实现代码的复用,而且可以实现代码的一致性。一致性是指,只要修改函数内部的代码,则所有调用函数的地方都能得到体现。

程序由一个个任务组成,函数就代表一个任务或者一个功能;函数是代码复用的通用机制。

4.1 Python 函数的分类

  1. 内置函数
    内置函数可以直接使用,比如 str( )、len( )、ord( )、chr( )、sorted( )等。
  2. 标准库函数
    可以通过 import 语句导入库,然后使用其中定义的函数。
  3. 第三方库函数
    Python 社区也提供了很多高质量的库,下载安装这些库后,通过 import 语句导入后,就可以使用这些第三方库的函数。
  4. 用户自定义函数
    开发中适应用户自身需求定义的函数。

4.2 函数的定义和调用

Python 中,函数定义的语法如下:

def 函数名([参数列表]):
    '''注释'''
    函数体
    return 返回值

Python 执行 def 时,会创建一个函数对象,绑定到函数名变量上;
参数不需要指定数据类型,有多个参数时,用逗号分隔;无参数,括号也必须保留;
返回值不需要指定数据类型;通过 return 可以结束函数并返回值;无 return 时,返回 None 值;返回多个值使用列表、元组、字典、集合即可。
调用函数前,必须先定义函数。内置函数对象会自动创建,标准库和第三方库函数,通过 import 导入模块时,会执行模块中的 def 语句。

函数调用的语法如下:

函数名([参数列表])
# 定义函数,就是定义了一个变量 test,指向了函数对象
def test():
    print('My first function')

# 调用函数
test()

# 函数对象,将 test 引用的函数对象赋值给 fun 变量
fun = test
# 通过 fun 调用函数
fun()

4.3 函数的参数

形参属于局部变量,只在函数范围内可见。

4.3.1 参数传递

参数传递的本质:将形参赋值给实参。Python 中,一切皆对象,所有的赋值操作都是“引用的传递”,所以参数的传递都是“引用传递”,不存在“值传递”。具体操作时分为两类:

  1. 对可变对象的修改,直接作用于原对象本身,如列表、字典、集合、自定义对象;
  2. 对不可变对象的修改,会创建对象的浅拷贝,再对拷贝对象进行操作(达到“值传递”的效果,但是不是“值传递”),如元组、字符串、整数、浮点数、function。
# 传递可变对象
my_list = [10, 20]
print(id(my_list))
print(my_list)


def mutable_para(li):
    print(id(li))
    li.append(30)


mutable_para(my_list)
print(my_list)

-------------执行结果--------------
1515465441216
[10, 20]
1515465441216
[10, 20, 30]
# 传递不可变对象
num = 100
print(f'{num} : {id(num)}')


def immutable_para(n):
    print(f'{n} : {id(n)}')     # 传进来的是 num 对象的引用
    n += 200                    # 由于 num 是不可变对象,创建了拷贝对象
    print(f'{n} : {id(n)}')     # n 已经变成新对象了


immutable_para(num)
print(f'{num} : {id(num)}')

-------------执行结果--------------
100 : 140708722840336
100 : 140708722840336
300 : 2197745400848
100 : 140708722840336

浅拷贝和深拷贝

浅拷贝,使用内置函数 copy(),不拷贝子对象的内容,只拷贝子对象的引用,对子对象的修改会影响源对象。
深拷贝,使用内置函数 deepcopy(),会将子对象的内容也全部拷贝一份,对子对象的修改不会影响源对象。

python使用库函数属于sdk开发吗 python库函数源代码_python使用库函数属于sdk开发吗

import copy


def test_copy():
    """测试浅拷贝"""
    a = [10, 20, [1, 2]]
    b = copy.copy(a)

    print(f'a : {a}')
    print(f'b : {b}')

    b.append(30)
    b[2].append(3)
    print(f'a : {a}')
    print(f'b : {b}')


test_copy()
-------------执行结果--------------
a : [10, 20, [1, 2]]
b : [10, 20, [1, 2]]
a : [10, 20, [1, 2, 3]]
b : [10, 20, [1, 2, 3], 30]

python使用库函数属于sdk开发吗 python库函数源代码_开发语言_02

import copy


def test_deep_copy():
    """测试深拷贝"""
    a = [10, 20, [1, 2]]
    b = copy.deepcopy(a)

    print(f'a : {a}')
    print(f'b : {b}')

    b.append(30)
    b[2].append(3)
    print(f'a : {a}')
    print(f'b : {b}')


test_deep_copy()
-------------执行结果--------------
a : [10, 20, [1, 2]]
b : [10, 20, [1, 2]]
a : [10, 20, [1, 2]]
b : [10, 20, [1, 2, 3], 30]

4.3.2 位置参数

位置参数,是按照从左到右的顺序定义的参数,调用时实参与形参按照顺序一一对应;位置参数调用时必须被传值。

def name_para(a, b, c):
    print(a if c > 0 else b)


name_para(10, 20, 0)  # 位置参数

4.3.3 关键字参数

函数调用时,按照 key=value 的形式传入实参,可以不按照顺序进行传参。

注意:

  1. 关键字参数必须在位置参数右边;
  2. 对同一个参数不能重复传值。
def name_para(a, b, c):
    print(a if c > 0 else b)


name_para(c=1, a=100, b=200)  # 关键字参数

4.3.4 默认值参数

默认值参数,就是定义时设置了默认值的参数。

注意:

  1. 默认值参数在定义时必须放在位置参数的右边;
  2. 调用时是可选的。
def default_para(a, b, c=1):	# c 为默认值参数
    return a if c == 1 else b


print(default_para(10, 20, 0))  # 传入默认值参数
print(default_para(10, 20))     # 不传入默认值参数

4.3.5 可变长度参数

可变长度参数,是指调用时实参个数不固定的参数。

其实调用时,实参只有位置参数和关键字参数,所以要提供两种解决方案分别处理:

  1. 可变长度的位置参数:使用 *(一个星号)接收,将多个溢出的位置参数放到一个“元组”对象中;
  2. 可变长度的关键字参数:使用 **(两个星号)接收,将多个溢出的关键字参数放到一个“字典”对象中。

可变参数 *args 与 **kwargs 通常是组合在一起使用的,如果一个函数的形参为 (*args, **kwargs),代表该函数可以接收任何形式、任意长度的参数。
实参中 * 和 ** 的作用:打回原形。如 fun(*(1, 3)) 就代表 fun(1, 3),fun(**{‘a’:1, ‘b’:2}) 就代表 fun(a=1, b=2)。

def mutable_para_num1(a, *b):  # 可变长位置参数
    print(a, b)


mutable_para_num1(1, 2, 3, 4)

---------------执行结果-----------------
1 (2, 3, 4)
def mutable_para_num2(a, **b):  # 可变长关键字参数
    print(a, b)


mutable_para_num2(1, name='lw', age=30)

---------------执行结果-----------------
1 {'name': 'lw', 'age': 30}
def mutable_para_num3(a, *args, **kwargs):
    print(a, b, c)


mutable_para_num3(1, 2, 3, 4, name='lw', age=30)

---------------执行结果-----------------
1 (2, 3, 4) {'name': 'lw', 'age': 30}

4.3.6 命名关键字参数

在可变长参数后面还有其他参数时,必须是“命名关键字参数”。

def force_named_para(*args, b, c):
    print(args, b, c)


force_named_para(1, 2, c=4, b=3)

---------------执行结果-----------------
(1, 2) 3 4

4.4 函数的注释

函数应该尽可能见名知意,也可以在函数内部的第一行写文档字符串,叫函数的注释,用 ''' 或者 """ 来表示。

def print_max(a, b):
    """打印 a 和 b 中的较大值"""
    print('较大值', a if a > b else b)

可以使用 help(函数名.__doc__) 打印输出函数的注释,执行结果如下:

No Python documentation found for '打印 a 和 b 中的较大值'.

4.5 命名空间与作用域

python使用库函数属于sdk开发吗 python库函数源代码_python使用库函数属于sdk开发吗_03

4.5.1 命名空间

命名空间,即存放名字与对象映射关系的地方。定义 x=“abc”,Python 会申请内存空间存放对象 abc,然后将名字 x 与 “abc” 的绑定关系存放于名称空间中,del x 表示清除该绑定关系。

三种命名空间

在程序执行期间最多会存在三种名称空间:

  1. 内置命名空间
    随着 Python 解释器的启动而产生,随着 Python 解释器的关闭而销毁,是第一个被加载的命名空间,用来存放一些内置名称。
  2. 全局命名空间
    随着 Python 文件的执行而产生,文件执行完毕而销毁,是第二个被加载的命名空间,用来存放文件执行过程中产生的名称。
  3. 局部命名空间
    随着函数的调用而产生,函数的结束而销毁,用来存放函数的形参以及函数内定义的变量。


命名空间的加载顺序

内置命名空间 > 全局命名空间 > 局部命名空间

名字的查找顺序

局部命名空间 > 全局命名空间 > 内置命名空间

4.5.2 作用域

变量起作用的范围称为变量的作用域,不同作用域内的同名变量互不影响。变量按照作用域分为:全局变量(内置名称空间与全局名称空间)和局部变量(局部名称空间)。

作用域关系是在函数定义阶段就已经固定的,与函数的调用位置无关。

全局变量

在函数和类之外定义的变量,位于内置命名空间和全局命名空间,从定义位置开始全局有效。
全局变量降低了函数的通用性和可读性,应尽量避免使用,一般用作常量的定义。
函数内要改变全局变量,需要使用 global 关键字。
使用 globals() 可以获取所有全局变量,结果为字典。

局部变量

在函数和类中定义的变量,位于局部命名空间,作用域为定义的函数或者类。
局部变量的引用比全局变量快,优先考虑使用。
如果局部变量和全局变量同名,则在函数内部隐藏全局变量,只使用同名的局部变量。
使用 locals() 可以获取所有局部变量,结果为字典。在全局作用域 locals() 与 globals() 的结果相同。

num = 100	# 声明全局变量

def test_variable():
    num = 200	# 声明局部变量,隐藏全局变量
    print(num)

test_variable()

----------执行结果----------
200


num = 100 	# 声明全局变量

def test_variable():
    global num  	# 在函数内部改变全局变量,使用 global 关键字
    print(num)
	num = 300

test_variable()

----------执行结果----------
100

LEGB 规则

Python 在查找“名称”时,按照 LEGB 规则查找,即 Local > Enclosed > Global > Built in

Local – 函数或者类的方法内部;
Enclosed – 嵌套函数,闭包;
Global – 模块中的全局变量;
Built in – Python 中的内置名称。

如果某个名称按照 LEGB 规则没有找到,就会产生 NameError 错误。

4.6 匿名函数

匿名函数,就是没有名字的函数,一次性使用。通常使用 lambda 表达式来声明。
lambda 表达式只允许包含一个表达式,不能包含复杂语句,该表达式的计算结果就是函数的返回值。

lambda 表达式语法:
lambda arg1,arg2,arg3... : 表达式

fun = lambda a, b: a * b
print(fun)
print(fun(2, 3))

------------------执行结果-----------------
<function <lambda> at 0x0000019CADF17280>
6


li = [lambda a: a * 2, lambda b: b * 3, lambda c: c * 4]
print(li[0](2), li[1](3), li[2](4))

------------------执行结果-----------------
4 9 16
salaries = {
    'siry': 3000,
    'tom': 7000,
    'lili': 10000,
    'jack': 5000
}

max(salaries, key=lambda k: salaries[k]) # 获取薪资最高的人名
sorted(salaries, key=lambda k: salaries[k], reverse=True) # 按降序排列薪资

4.7 递归函数

递归函数是指在函数内部调用自己,必须包含2个部分:

  1. 终止条件:表示递归在什么时候结束,不再调用自己;
  2. 递归步骤:第 n 次调用与第 n-1 次调用需要有关联。

递归函数在调用时会创建大量的函数对象,极其消耗内存和运算能力。在处理大数据量时,谨慎使用。
Python中,递归层级限制默认为 1000,可通过 sys.getrecursionlimit() 获取,也可通过 sys.setrecursionlimit(num) 设置递归层级。

def factorial(n):
    """计算阶乘"""
    if n == 1:
        return 1
    else:
        return n * factorial(n - 1)


print(factorial(5))


def accumulation(n):
    """计算累加和"""
    if n == 1:
        return 1
    else:
        return n + accumulation(n - 1)


print(accumulation(100))

4.8 嵌套函数

在函数内部定义的函数,只能在定义的函数中调用,外面调用不了。用于专门给外部函数提供服务的场景。

def f1():
    print('f1 running')

    def f2():
        print('f2 running')

    f2()	# 只能在 f1 内部调用


f1()

在内部函数中,修改外部函数的变量需要用 nonlocal 关键字声明。

def f1():
    b = 10

    def f2():
        nonlocal b	# 声明外部函数的局部变量
        print(f'inner b: {b}')
        b = 20

    f2()
    print(f'outer b: {b}')


f1()
------------------执行结果-----------------
inner b: 10
outer b: 20

4.9 闭包函数

闭包函数是一个记录了定义它的作用域信息的函数。

闭包函数必须满足以下3个条件:

  1. 定义在函数内部的函数;
  2. 内部函数包含对外部函数作用域中变量的引用;
  3. 通常将闭包函数通过 return 语句返回,可以在任意位置调用产生同样的结果。
def outer():
    x = 10

    def inner():  # 闭包函数
        print(x)

    return inner

闭包函数的用途

给函数传值有两种方式:一是通过参数传值,二是通过闭包将值包给函数。

import requests

# 方式一
def get(url):
    return requests.get(url).text

# 方式二
def page(url):
    def get():
        return requests.get(url).text

    return get

对比两种传值方式,如果爬取同一页面时,方式一需要重复传入 url,而方式二只需要传入一次,就会得到一个包含指定 url 的闭包,以后调用该闭包就无需再传值。

# 方式一爬取同一网页
get('https://www.baidu.com')
get('https://www.baidu.com')
get('https://www.baidu.com')

# 方式二爬取同一网页
baidu = page('https://www.baidu.com')
baidu()
baidu()
baidu()