Python学习笔记(四) 函数

简介: Python学习笔记(四) 函数

这篇文章介绍有关 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 中,变量赋值实际上并不是将值储存在变量中,反而有点像将变量名贴在值上

以下的图片展示了变量赋值的一种直观理解

24f0025f2c4ca203cabf9058f6cf8b62_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dzbXJ6eA==,size_16,color_FFFFFF,t_70#pic_center.png


(2)变量修改


在 Python 中,变量可以分为可变类型和不可变类型,当修改可变类型时,直接修改物体内的值

当修改不可变类型时,由于物体内的值不能改变,所以 Python 采用一种特殊的处理方式

即将标签贴在另一个储存了新内容的物体,储存了旧内容的物体将会在没有标签指向时被系统自动回收

以下的图片展示了变量修改的一种直观理解

175cb11a719893625057810f91f59bfa_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dzbXJ6eA==,size_16,color_FFFFFF,t_70#pic_center.png


(3)参数传递方式


传入函数的参数分两种情况考虑,一种是传入可变类型,另一种是传入不可变类型

常见的可变类型有 listdict,常见的不可变类型有 tuplestringintfloatbool

  • 如果的是传入 可变类型,那么直接修改形参会改变传入的实参
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'

以下的图片展示了以上两种情况的一种直观理解

ae88e3aa58f0c6930f8c81781a294093_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dzbXJ6eA==,size_16,color_FFFFFF,t_70#pic_center.png


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()

内层作用域可以访问外层作用域的变量,但外层作用域不能访问内层作用域的变量,这是一个单向的过程

在访问一个变量时,首先在当前作用域搜索,如果没有找到,则在下一个外层作用域搜索,直到最后一层

若在最外层作用域也没有找到该变量,就会抛出引用错误,下图是一个直观的理解

714f8473ea312bdd09b68691cc15fd7e_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dzbXJ6eA==,size_16,color_FFFFFF,t_70#pic_center.png

下面来看几个例子,理解一下这个概念

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

文章知识点与官方知识档案匹配,可进一步学习相关知识

目录
相关文章
|
2月前
|
PHP Python
Python format()函数高级字符串格式化详解
在 Python 中,字符串格式化是一个重要的主题,format() 函数作为一种灵活且强大的字符串格式化方法,被广泛应用。format() 函数不仅能实现基本的插入变量,还支持更多高级的格式化功能,包括数字格式、对齐、填充、日期时间格式、嵌套字段等。 今天我们将深入解析 format() 函数的高级用法,帮助你在实际编程中更高效地处理字符串格式化。
163 0
|
2月前
|
索引 Python 容器
[oeasy]python096_列表_计数函数_count
本教程详细介绍了Python中列表的计数方法`count`,包括其基本用法、与`len`函数的区别,以及如何结合索引操作查找和删除特定元素。同时探讨了字符串对象的`count`方法,并通过实例演示了如何统计字符出现次数。
45 7
|
1月前
|
数据采集 索引 Python
Python Slice函数使用教程 - 详解与示例 | Python切片操作指南
Python中的`slice()`函数用于创建切片对象,以便对序列(如列表、字符串、元组)进行高效切片操作。它支持指定起始索引、结束索引和步长,提升代码可读性和灵活性。
|
2月前
|
机器学习/深度学习 数据处理 索引
Python内置函数:面试通关的49个秘密武器
本文精选49个Python高频面试内置函数,涵盖数值处理、类型转换、序列操作、字典集合、函数式编程及高级特性,结合真实代码案例解析底层逻辑与应用场景,助你提升开发效率,轻松应对技术面试。
52 0
|
5月前
|
人工智能 索引 Python
[oeasy]python091_列表_索引_index_中括号_索引函数
本文介绍了Python中列表与字符串的索引及index函数用法。通过range生成列表,使用索引[]访问和修改列表元素,index函数查找元素位置。字符串支持索引访问但不可直接修改。还探讨了16进制数在Python中的表示方法,以及日期、月份等特殊字符的Unicode范围。最后总结了列表与字符串操作的区别,并预告后续内容,提供蓝桥云课、GitHub和Gitee链接供进一步学习。
107 20
|
3月前
|
API Python
Python 的内建函数
Python 的内置函数列表,方便查询使用方法。
|
3月前
|
数据采集 自然语言处理 搜索推荐
Python内置函数ord()详解
`ord()` 是 Python 中用于将单个字符转换为对应 Unicode 码点的核心函数,支持 ASCII、多语言字符及特殊符号。其返回值为整数(范围 0-1114111),适用于字符编码验证、数据清洗、自定义排序、基础加解密等场景。使用时需注意参数长度必须为 1,否则会触发 `TypeError`。结合 `chr()` 函数可实现双向转换,进阶技巧包括多字节字符处理、编码范围检测及字符分类验证等。
|
5月前
|
Python
[oeasy]python086方法_method_函数_function_区别
本文详细解析了Python中方法(method)与函数(function)的区别。通过回顾列表操作如`append`,以及随机模块的使用,介绍了方法作为类的成员需要通过实例调用的特点。对比内建函数如`print`和`input`,它们无需对象即可直接调用。总结指出方法需基于对象调用且包含`self`参数,而函数独立存在无需`self`。最后提供了学习资源链接,方便进一步探索。
109 17
|
5月前
|
人工智能 Python
[oeasy]python083_类_对象_成员方法_method_函数_function_isinstance
本文介绍了Python中类、对象、成员方法及函数的概念。通过超市商品分类的例子,形象地解释了“类型”的概念,如整型(int)和字符串(str)是两种不同的数据类型。整型对象支持数字求和,字符串对象支持拼接。使用`isinstance`函数可以判断对象是否属于特定类型,例如判断变量是否为整型。此外,还探讨了面向对象编程(OOP)与面向过程编程的区别,并简要介绍了`type`和`help`函数的用法。最后总结指出,不同类型的对象有不同的运算和方法,如字符串有`find`和`index`方法,而整型没有。更多内容可参考文末提供的蓝桥、GitHub和Gitee链接。
110 11
|
5月前
|
开发框架 Java .NET
Python中main函数:代码结构的基石
在Python中,`main`函数是程序结构化和模块化的重要组成部分。它实现了脚本执行与模块导入的分离,避免全局作用域污染并提升代码复用性。其核心作用包括:标准化程序入口、保障模块复用及支持测试驱动开发(TDD)。根据项目复杂度,`main`函数有基础版、函数封装版、参数解析版和类封装版四种典型写法。 与其他语言相比,Python的`main`机制更灵活,支持同一文件作为脚本运行或模块导入。进阶技巧涵盖多文件项目管理、命令行参数处理、环境变量配置及日志集成等。此外,还需注意常见错误如全局变量污染和循环导入,并通过延迟加载、多进程支持和类型提示优化性能。
347 0

热门文章

最新文章

推荐镜像

更多