这篇文章介绍有关 Python 函数一些常被大家忽略的知识点,帮助大家更全面地掌握 Python
1、函数文档
在 def
语句后添加的字符串被称为 文档字符串,它是函数的说明,将作为函数的一部分储存起来
def multiply(x, y):
'''Calculates the multiplication between x and y
Args:
x (Number): the multiplicand
y (Number): the multiplier
Returns:
Number: the result of multiplication
'''
return x * y
既可以通过内置属性 __doc__
访问文档字符串
print(multiply.__doc__)
# Calculates the multiplication between x and y
#
# Args:
# x (Number): the multiplicand
# y (Number): the multiplier
#
# Returns:
# Number: the result of multiplication
也可以通过内置方法 help()
访问文档字符串
help(multiply)
# Help on function multiply in module __main__:
#
# multiply(x, y)
# Calculates the multiplication between x and y
#
# Args:
# x (Number): the multiplicand
# y (Number): the multiplier
#
# Returns:
# Number: the result of multiplication
2、参数传递方式
一般来说,函数参数的传递方式有两种,一种是按值传递,另一种是按引用传递
那么在 Python 中,函数参数的传递方式是什么呢?要解释这个问题,我们要从变量开始讲起
(1)变量赋值
在 Python 中,变量赋值实际上并不是将值储存在变量中,反而有点像将变量名贴在值上
以下的图片展示了变量赋值的一种直观理解
(2)变量修改
在 Python 中,变量可以分为可变类型和不可变类型,当修改可变类型时,直接修改物体内的值
当修改不可变类型时,由于物体内的值不能改变,所以 Python 采用一种特殊的处理方式
即将标签贴在另一个储存了新内容的物体,储存了旧内容的物体将会在没有标签指向时被系统自动回收
以下的图片展示了变量修改的一种直观理解
(3)参数传递方式
传入函数的参数分两种情况考虑,一种是传入可变类型,另一种是传入不可变类型
常见的可变类型有 list
、dict
,常见的不可变类型有 tuple
、string
、int
、float
、bool
- 如果的是传入 可变类型,那么直接修改形参会改变传入的实参
def change(strings):
strings[0] = 'Function'
names = ['Andy']
change(names)
print(names)
# ['Function']
- 如果传入的是 不可变类型,那么直接修改形参不会改变传入的实参
def change(string):
string = 'Function'
name = 'Andy'
change(name)
print(name)
# 'Andy'
以下的图片展示了以上两种情况的一种直观理解
3、位置参数与关键字参数
我们一般使用的参数称为 位置参数,因为在调用函数时可以忽略它们的名称,只需要位置对应即可
def greet(greeting, name):
print(greeting, name)
greet('Hello', 'World')
# Hello World
如果参数很多,难以记住每个参数的位置,可以在调用函数时指定参数的名称,这称为 关键字参数
def greet(greeting, name):
print(greeting, name)
greet(name = 'World', greeting = 'Hello')
# Hello World
4、收集参数与分配参数
下面考虑一种场景,如果参数的数量是不确定的,我们该怎么处理呢?这时我们可以使用 收集参数
定义函数时,如果在参数前加上星号 *
,那么调用函数时,自动把余下的参数作为一个元组储存在该参数
def test(a, b, *c):
print(a, b, c)
test(1, 2, 3, 4, 5, 6)
# 1 2 (3, 4, 5, 6)
一般情况下,我们会把收集参数指定为最后一个参数
但假如我们把收集参数放在前面会发生什么呢?比如说像这样
def test(a, *b, c):
print(a, b, c)
test(1, 2, 3, 4, 5, 6)
# TypeError: test() missing 1 required keyword-only argument: 'c'
参数前带有星号意味着,该形参会接收余下的所有实参,从而导致后面的形参无法接收实参而产生错误
但我们可以用关键字参数解决这个问题,即我们给收集参数后的所有形参直接指定实参
def test(a, *b ,c):
print(a, b, c)
test(1, 2, 3, 4, 5, c = 6)
# 1 (2, 3, 4, 5) 6
到目前为止,使用 单星号 *
还存在一个问题,即它无法收集关键字参数
def test(*paras):
print(paras)
test(1, 2, 3) # 收集位置参数,正常
# (1, 2, 3)
test(x = 1, y = 2, z = 3) # 收集关键字参数,错误
# TypeError: test() got an unexpected keyword argument 'x'
解决方案就是使用 双星号 **
,此时得到的将会是一个字典,而非元组
def test(**paras):
print(paras)
test(x = 1, y = 2, z = 3) # 收集关键字参数,正常
# {'x': 1, 'y': 2, 'z': 3}
test(1, 2, 3) # 收集位置参数,错误
# TypeError: test() takes 0 positional arguments but 3 were given
使用单星号可以收集位置参数但不能收集关键字参数,使用双星号可以收集关键字参数但不能收集位置参数
所以我们可以使用单星号和双星号同时收集位置参数和关键字参数
def test(*args, **kwargs):
print(args, kwargs)
test(1, 2, 3, x = 4, y = 5, z = 6)
# (1, 2, 3) {'x': 4, 'y': 5, 'z': 6}
分配参数 与收集参数执行相反的操作
在调用函数时用单星号 *
或双星号 **
将元组或字典拆成独立的参数后赋值
def test(x, y):
print(x, y)
paras1 = (1, 2)
test(*paras1) # 将元组变成独立的位置参数
# 1 2
paras2 = {'x': 1, 'y': 2}
test(**paras2) # 将字典变成独立的关键字参数
# 1 2
5、作用域与作用域链
变量是什么?其实可以将变量视为指向值的名称,这或许有点像字典中的键值对(在字典中键指向值)
实际上,变量的确是在使用一种看不见的字典,我们可以使用内置函数返回这个看不见的字典
globals()
:返回模块名称空间的字典locals()
:返回当前名称空间的字典
a = 0
def func():
b = 0
print('1', locals())
print('2', globals())
func()
print('3', locals())
print('4', globals())
# 结果如下(以下结果省略了部分内置的变量)
# 1 {'b': 0}
# 2 {'a': 0, 'func': <function func at 0x038C0810>}
# 3 {'a': 0, 'func': <function func at 0x038C0810>}
# 4 {'a': 0, 'func': <function func at 0x038C0810>}
# 由此可知
# 1. 如果在同一模块下,globals() == globals() 永远成立
# 2. 如果在全局环境下,locals() == globals() 也会成立
这个看不见的字典被称为 作用域,作用域可以分为内建作用域、全局作用域和局部作用域
每个函数都有一个独立的 局部作用域,当函数出现嵌套时,就会出现层层嵌套的作用域,称为 作用域链
# 内建作用域:内置函数的命名空间
# 全局作用域
a = 1
print('a', a) # 1
print('b', b) # NameError
print('c', c) # NameError
def outer():
# 局部作用域 (outer)
b = 2
print('a', a) # 1
print('b', b) # 2
print('c', c) # NameError
def inner():
# 局部作用域 (inner)
c = 3
print('a', a) # 1
print('b', b) # 2
print('c', c) # 3
inner()
outer()
内层作用域可以访问外层作用域的变量,但外层作用域不能访问内层作用域的变量,这是一个单向的过程
在访问一个变量时,首先在当前作用域搜索,如果没有找到,则在下一个外层作用域搜索,直到最后一层
若在最外层作用域也没有找到该变量,就会抛出引用错误,下图是一个直观的理解
下面来看几个例子,理解一下这个概念
p = 'F'
def func1():
p = 'W'
q = 'H'
print('func1 p:', p)
print('func1 q:', q)
func1()
def func2():
print('func2 p:', p)
print('func2 q:', q)
func2()
# 结果如下
# func1 p: W
# func1 q: H
# func2 p: F
# func2 q: NameError
# 分析如下
# 在 func1 中访问 p:局部作用域 func1 (√)
# 在 func1 中访问 q:局部作用域 func1 (√)
# 在 func2 中访问 p:局部作用域 func2 (×) -> 全局作用域 (√)
# 在 func2 中访问 q:局部作用域 func2 (×) -> 全局作用域 (×) -> 内建作用域 (×) -> 报错
# ×
# /
# |— — — — — — — — — — — — — — — — — — — — — — — — — —|/
# | global /
# | p = 'F' √ /|
# | / / |
# | |— — — — — — — — — —| |— — — — — — — —/— —|/ |
# | | func1 √ | | func2 / / |
# | | p = 'W' / | | / /| |
# | | q = 'H' / √ | | / / | |
# | | / / | | / / | |
# | | print(p) / | | print(p) / | |
# | | / | | / | |
# | | print(q) | | print(q) | |
# | |— — — — — — — — — —| |— — — — — — — — — —| |
# |— — — — — — — — — — — — — — — — — — — — — — — — — —|
# 由此可知
# 1. 访问变量的顺序从当前作用域开始向外搜索,直到最外层作用域为止
# 2. 在局部作用域中的变量会覆盖全局作用域中的同名变量
# 3. 不同局部作用域间的变量互不影响
p = 'Hi'
def func1():
p = 'Hello'
print('func1 p:', p)
func1()
print('global p:', p)
q = 'Hi'
def func2():
global q
q = 'Hello'
print('func2 q:', q)
func2()
print('global q:', q)
# 结果如下
# func1 p: Hello
# global p: Hi
# func2 q: Hello
# global q: Hello
# 分析如下
# 在 func1 中,p = 'Hello' 在局部作用域创建变量 p 并将其赋值为 'Hello'
# 因此,在局部作用域中访问变量 p 时,能直接引用局部作用域中的变量 p
# 并且,在全局作用域中访问变量 p 时,其值不变,因为全局作用域中的 p 没有被修改
# 在 func2 中,q = 'Hello' 前通过 global 关键字声明 q 直接引用全局变量,后将其修改为 'Hello'
# 因此,在局部作用域中访问变量 q 时,其引用的是全局作用域中的变量 q
# 并且,在全局作用域中访问变量 q 时,其值改变,因为全局作用域中的 q 在局部作用域被修改
# 由此可知
# 1. 局部作用域中的赋值语句左边出现与全局变量同名的变量,则会在局部作用域创建变量而非引用全局变量
# 2. 局部作用域中使用 global 关键字能明确声明该变量引用全局变量
6、lambda 函数
lambda 函数即 匿名函数,可以省去函数命名的烦恼,对于一些功能简单的函数尤为合适
其基本语法如下:lambda parameters: expression
parameters
:用逗号分隔的变量列表,可选,代表传入函数的参数expression
:简单语句,不用包含return
,代表函数的返回结果
示例 1:实现简单的加法
# 输入 x、y,输出 x + y
add = lambda x, y: x + y
# 能像使用正常函数一样使用 lambda 函数
z = add(1, 2) # 3
示例 2:使用在 sorted
函数中,指定排序规则
# 按绝对值排序
result = sorted([3, -2, 4, -1, 5], key = lambda x: abs(x))
print(result) # [-1, -2, 3, 4, 5]
示例 3:使用在 filter
函数中
# 过滤奇数
result = list(filter(lambda x: x % 2 == 0, range(10)))
print(result) # [0, 2, 4, 6, 8]
示例 4:使用在 map
函数中
# 对每个元素求平方
result = list(map(lambda x: x**2, range(10)))
print(result) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
示例 5:使用在 reduce
函数中
# 累加求和
from functools import reduce
result = reduce(lambda x, y: x + y, range(10))
print(result) # 45