《Python 基础篇》五:函数
Author: ACatSmiling
Since: 2024-09-27
函数简介
函数
:也是一个对象,对象是内存中专门用来存储数据的一块区域。函数可以用来保存一些可执行的代码,并且可以在需要时,对这些语句进行多次的调用。
创建函数:
def 函数名([形参1, 形参2, ... 形参n]): 代码块
- 函数名必须要符号标识符的规范(可以包含字母、数字、下划线,但是不能以数字开头)。
- 函数中保存的代码不会立即执行,需要调用函数代码才会执行。
调用函数:
函数名()
- 定义函数一般都是要实现某种功能的。
示例:
# 定义一个函数 def fn(): print('这是我的第一个函数!') print('hello') print('今天天气真不错!') # 打印 fn print(fn) # <function fn at 0x00000175FD0261F0> print(type(fn)) # <class 'function'> # fn 是函数对象,fn() 调用函数 # print 是函数对象,print() 调用函数 fn() # 定义一个函数,可以用来求任意两个数的和 def sum(): a = 123 b = 456 print(a + b) sum() # 579 # 定义函数时指定形参 def fn2(a, b): print(a, "+", b, "=", a + b) # 调用函数时,来传递实参 fn2(10, 20) # 10 + 20 = 30 fn2(123, 456) # 123 + 456 = 579
函数的参数
在定义函数时,可以在函数名后的()
中定义数量不等的形参,多个形参之间使用,
隔开。
形参(形式参数)
:定义形参就相当于在函数内部声明了变量,但是并不赋值。实参(实际参数)
:调用函数时,传入的真正的参数值。- 如果函数定义时,指定了形参,那么在调用函数时也必须传递实参, 实参将会赋值给对应的形参,简单来说,有几个形参就得传几个实参。
# 求任意三个数的乘积 def mul(a, b, c): print(a * b * c) mul(1, 2, 3) # 6 # 根据不同的用户名显示不同的欢迎信息 def welcome(username): print('欢迎 ', username, ' 光临') welcome('孙悟空') # 欢迎 孙悟空 光临
参数的传递方式
函数的传参方式有多种:
默认值参数
:定义形参时,可以为形参指定默认值。指定了默认值以后,如果用户传递了参数,则默认值没有任何作用;如果用户没有传递参数,则默认值就会生效。位置参数
:即将对应位置的实参复制给对应位置的形参。关键字参数
:可以不按照形参定义的顺序去传递,而直接根据参数名去传递参数。- 位置参数和关键字参数可以混合使用,混合使用时,必须将位置参数写到前面。
- 函数在调用时,解析器不会检查实参的类型,实参可以传递任意类型的对象。
- 在函数中对形参进行重新赋值,不会影响其他的变量。
- 如果形参指向的是一个对象,当我们通过形参去修改对象时,会影响到所有指向该对象的变量。
示例:
# 定义函数,并为形参指定默认值 def fn(a=5, b=10, c=20): print('a =', a) print('b =', b) print('c =', c) fn(1, 2, 3) fn(1, 2) fn() # 位置参数,根据位置传递对应的实参 fn(1, 2, 3) # 关键字参数,根据形参名传递对应的实参 fn(b=1, c=2, a=3) print('hello', end='') # 位置参数和关键字参数混合使用,位置参数必须在关键字参数前面 fn(1, c=30) def fn2(a): print('a =', a) # 函数在调用时,解析器不会检查实参的类型 # 实参可以传递任意类型的对象 b = 123 fn2(b) # a = 123 b = True fn2(b) # a = True b = 'hello' fn2(b) # a = hello b = None fn2(b) # a = None b = [1, 2, 3] fn2(b) # a = [1, 2, 3] fn2(fn) # a = <function fn at 0x0000025AB8B561F0> # 类型不检查的缺陷,在传参时需要额外注意 def fn3(a, b): print(a + b) # fn3(123, "456") # TypeError: unsupported operand type(s) for +: 'int' and 'str' # 在函数中对形参进行重新赋值,不会影响其他的变量 def fn4(a): a = 20 print('a =', a, id(a)) # a = 20 140708519029120 c = 10 fn4(c) print(c) # 10 # 如果形参指向的是一个对象,当我们通过形参去修改对象时,会影响到所有指向该对象的变量 def fn5(a): # a 是一个列表,尝试修改列表中的元素 a[0] = 30 print('a =', a, id(a)) # a = [30, 2, 3] 2056358531968 c = [1, 2, 3] fn5(c) print('c =', c, id(c)) # c = [30, 2, 3] 2056358531968 # 通过浅复制,或者切片,实现不修改 c 本身 fn4(c.copy()) fn4(c[:])
Java 的参数传递机制
在 Java 中,基本数据类型(如 int、double、char 等)是按值传递的,即传递的是变量的值的副本。对于引用数据类型(如对象、数组等),实际上传递的是对象引用的副本,但从效果上看,可以通过这个引用副本去操作对象本身。
Python 的参数传递机制
Python 的参数传递通常被认为是 "对象引用传递"。不可变对象(如整数、字符串、元组等)在函数内部的行为看起来像是值传递,因为不能直接修改不可变对象,一旦尝试修改,实际上是创建了新的对象。而可变对象(如列表、字典、集合等)在函数内部的修改会影响到外部的对象,因为传递的是对象的引用。
二者对比
基本数据类型的传递:
-
Java:对于基本数据类型(如 int、double、char 等),Java 是严格的值传递。这意味着当把一个基本数据类型的变量作为参数传递给方法时,会将该变量的值复制一份传递过去,在方法内部对这个参数的修改不会影响到原始变量的值。
public class JavaValuePassing { public static void main(String[] args) { int num = 10; changeNumber(num); System.out.println(num); // 输出仍然是 10 } public static void changeNumber(int n) { n = 20; } } -
Python:Python 中没有严格意义上的基本数据类型和引用数据类型的区分。但对于不可变的基本数据类型类似物(如整数、字符串等),在函数调用时的行为有点类似于值传递。然而,实际上是对象引用的传递,只是因为不可变对象一旦被修改就会创建新的对象,所以看起来像是值传递。
def change_number(num): num = 20 a = 10 change_number(a) print(a) # 输出仍然是 10
引用数据类型的传递:
-
Java:对于引用数据类型(如对象、数组等),Java 传递的是对象引用的副本。这意味着在方法内部可以通过这个引用副本去操作对象本身,修改对象的属性会影响到外部的对象。但是,如果将这个引用重新指向一个新的对象,不会影响到原始的引用。
class Person { String name; public Person(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } } public class JavaValuePassing { public static void main(String[] args) { Person person = new Person("Alice"); changePerson(person); System.out.println(person.getName()); // 可能输出修改后的名字 } public static void changePerson(Person p) { p.setName("Bob"); // 如果重新创建一个新的对象并赋值给参数 p // p = new Person("Charlie"); 这样不会影响到外部的 person 引用 } } -
Python:对于可变的引用数据类型类似物(如列表、字典、集合等),传递的是对象引用。在函数内部对这个对象的修改会影响到外部的对象。对于不可变的引用数据类型类似物(如元组),虽然传递的也是对象引用,但由于元组本身不可变,在函数内部无法修改其内容。
def change_list(lst): lst.append(20) my_list = [1, 2, 3] change_list(my_list) print(my_list) # 输出为 [1, 2, 3, 20]
小结:
- Java:
- Java 的值传递机制相对较为明确和严格,基本数据类型和引用数据类型的传递方式有明显的区分,并且对于引用数据类型的操作有一定的限制,不会因为意外的重新赋值而影响到外部的引用。
- 开发者在使用时可以比较清楚地知道参数传递的效果,不容易出现意外的错误。
- Python:
- Python 的参数传递机制更加灵活,但也可能因为这种灵活性而导致一些难以察觉的错误。开发者需要根据对象的可变性来理解参数传递的行为,并且要注意在函数内部对可变对象的修改可能会影响到外部的对象。
- Python 的这种特性在某些情况下可以使代码更加简洁和高效,但也需要开发者更加小心地处理参数传递和对象的修改。
不定长的参数
在定义函数时,可以在形参前边加上一个*
,这样这个形参将会获取到所有的实参,并将所有的实参保存到一个元组中。
- 带
*
的形参只能有一个。 - 带
*
的参数,可以和其他参数配合使用。 - 可变参数不是必须写在最后,但是注意,带
*
的参数后的所有参数,必须以关键字参数的形式传递,否则报错。 - 如果在形参的开头直接写一个
*
,则要求所有的参数必须以关键字参数的形式传递。 *
形参只能接收位置参数,而不能接收关键字参数。**
形参可以接收其他的关键字参数,它会将这些参数统一保存到一个字典中。字典的 key 就是参数的名字,字典的 value 就是参数的值。**
形参只能有一个,并且必须写在所有参数的最后。- 传递实参时,也可以在序列类型的参数前添加
*
,这样会自动将序列中的元素依次作为参数传递给函数,但要求序列中元素的个数必须和形参的个数一致。 - 如果是字典,通过
**
来进行解包操作。
# 定义一个函数,可以求任意个数字的和 def sum(*nums): # 定义一个变量,来保存结果 result = 0 # 遍历元组,并将元组中的数进行累加 for n in nums: result += n print(result) sum(10, 20, 30, 40) # 100 sum(10, 20, 30, 40, 50, 60, 70) # 280 # *a 会接受所有的位置实参,并且会将这些实参统一保存到一个元组中(装包) def fn(*a): print("a =", a, type(a)) fn(1, 2, 3) # a = (1, 2, 3) <class 'tuple'> fn(1, 2, 3, 4, 5) # a = (1, 2, 3, 4, 5) <class 'tuple'> # 带星号的形参只能有一个 # 带星号的参数,可以和其他参数配合使用 # 下面的函数,第一个参数给a,第二个参数给b,剩下的都保存到c的元组中 def fn2(a, b, *c): print('a =', a) print('b =', b) print('c =', c) fn2(1, 2, 3, 4, 5) # 可变参数不是必须写在最后,但是注意,带 * 的参数后的所有参数,必须以关键字参数的形式传递 # 下面的函数,第一个参数给 a,剩下的位置参数给 b 的元组,c 必须使用关键字参数 def fn3(a, *b, c): print('a =', a) print('b =', b) print('c =', c) fn3(1, 2, 3, 4, c=5) # 下面的函数,所有的位置参数都给 a,b 和 c 必须使用关键字参数 def fn4(*a, b, c): print('a =', a) print('b =', b) print('c =', c) fn4(1, 2, 3, b=4, c=5) # 如果在形参的开头直接写一个 *,则要求我们的所有的参数必须以关键字参数的形式传递 def fn5(*, a, b, c): print('a =', a) print('b =', b) print('c =', c) fn5(a=3, b=4, c=5) # * 形参只能接收位置参数,而不能接收关键字参数 # def fn3(*a) : # print('a =',a) # ** 形参可以接收其他的关键字参数,它会将这些参数统一保存到一个字典中 # 字典的 key 就是参数的名字,字典的 value 就是参数的值 # ** 形参只能有一个,并且必须写在所有参数的最后 def fn6(b, c, **a): print('a =', a, type(a)) print('b =', b) print('c =', c) fn6(b=1, c=2, h=3, e=10, f=20) fn6(6, 7, g=1, d=2, h=3, e=10, f=20) print('##########################################################') # 参数的解包(拆包) def fn7(a, b, c): print('a =', a) print('b =', b) print('c =', c) # 传递实参时,也可以在序列类型的参数前添加星号,这样他会自动将序列中的元素依次作为参数传递给函数 # 这里要求序列中元素的个数必须和形参的个数的一致 t = (10, 20, 30) fn7(*t) # 通过 **来对一个字典进行解包操作 d = {'a': 100, 'b': 200, 'c': 300} fn7(**d)
函数的返回值
函数的返回值就是函数执行以后返回的结果,可以通过return
来指定函数的返回值。
- return 后边可以跟任意的对象,甚至可以是一个函数。return 后边跟什么值,函数就会返回什么值。
- 如果仅仅写一个 return 或者不写 return,则相当于 return None。
- 在函数中,return 后的代码都不会执行,return 一旦执行函数自动结束。
示例:
# return 后边跟什么值,函数就会返回什么值 # return 后边可以跟任意的对象,返回值甚至可以是一个函数 def fn(): # return 'Hello' # return [1, 2, 3] # return {'k': 'v'} def fn1(): print('hello') return fn1 # 返回值也可以是一个函数 r = fn() # 这个函数的执行结果就是它的返回值 print(r) # <function fn.<locals>.fn1 at 0x000001F3430BCF70> r() # hello # 如果仅仅写一个 return 或者不写 return,则相当于 return None # 在函数中,return 后的代码都不会执行,return 一旦执行函数自动结束 def fn2(): a = 10 return print('abc') # 不会执行 r = fn2() print(r) # None def fn3(): for i in range(5): if i == 3: # break 用来退出当前循环 # continue 用来跳过当次循环 return # return 用来结束函数 print(i) print('循环执行完毕!') fn3() def fn4(): return 10 # fn4 和 fn4()的区别 print(fn4) # fn4 是函数对象,打印 fn4 实际是在打印函数对象:<function fn5 at 0x00000229B3CFD670> print(fn4()) # fn4() 是在调用函数,打印 fn4() 实际上是在打印 fn4() 函数的返回值:10
文档字符串
文档字符串
:在定义函数时,可以在函数内部编写文档字符串,文档字符串就是函数的说明。文档字符串非常简单,其实直接在函数的第一行写一个字符串就是文档字符串,当我们编写了文档字符串时,就可以通过help()
函数来查看函数的说明。
示例:
# help() 是 Python 中的内置函数 # 通过 help() 函数可以查询 Python 中的函数的用法 # 语法:help(函数对象) help(print) # 获取 print() 函数的使用说明 # 文档字符串(doc str) def fn(a: int, b: bool, c: str = 'hello') -> int: # 函数参数后跟着类型,返回值是一个 int """ 这是一个文档字符串的示例 函数的作用:。。。。。。 函数的参数: a,作用,类型,默认值。。。 b,作用,类型,默认值。。。 c,作用,类型,默认值。。。 """ return 10 help(fn)
作用域
作用域(scope)
:指的是变量生效的区域。在 Python 中一共有两种作用域:
全局作用域
:全局作用域在程序执行时创建,在程序执行结束时销毁。- 所有函数以外的区域都是全局作用域。
- 在全局作用域中定义的变量,都属于全局变量,全局变量可以在程序的任意位置被访问。
函数作用域
:函数作用域在函数调用时创建,在调用结束时销毁。- 函数每调用一次就会产生一个新的函数作用域。
- 在函数作用域中定义的变量,都是局部变量,它只能在函数内部被访问。
- 如果希望在函数内部修改全局变量,则需要使用 global 关键字,来声明变量。
变量的查找过程:当我们使用变量时,会优先在当前作用域中寻找该变量,如果有则使用;如果没有则继续去上一级作用域中寻找,如果有则使用;如果依然没有则继续去上一级作用域中寻找,以此类推,直到找到全局作用域,依然没有找到,则会抛出异常 "NameError: name 'a' is not defined"。
示例:
b = 20 # 全局变量 def fn(): a = 10 # a 定义在了函数内部,所以他的作用域就是函数内部,函数外部无法访问 print('函数内部:', 'a =', a) print('函数内部:', 'b =', b) fn() # print('函数外部:', 'a =', a) # NameError: name 'a' is not defined print('函数外部:', 'b =', b) def fn1(): def fn2(): print('fn3中:', 'a =', a) fn2() # fn1() # fn1 中的嵌套函数 fn2,找不到 a,报错 NameError: name 'a' is not defined a = 20 def fn3(): # a = 10 # 在函数中为变量赋值时,默认都是为局部变量赋值 # 如果希望在函数内部修改全局变量,则需要使用 global 关键字,来声明变量 global a # 声明在函数内部使用的 a 是全局变量,此时再去修改 a 时,就是在修改全局的 a a = 10 # 修改全局变量 print('函数内部:', 'a =', a) fn3() print('函数外部:', 'a =', a) # 函数外部: a = 10
命名空间
命名空间(namespace)
:指的是变量存储的位置,每一个变量都需要存储到指定的命名空间当中。
- 每一个作用域都会有一个它对应的命名空间。
- 全局命名空间,用来保存全局变量;函数命名空间,用来保存函数中的变量。
- 命名空间实际上就是一个字典,是一个专门用来存储变量的字典。
- 使用
locals()
函数,可以获取当前作用域的命名空间,其返回值是一个字典。- 如果在全局作用域中调用 locals() 则获取全局命名空间,如果在函数作用域中调用 locals() 则获取函数命名空间。
- 使用
globals()
函数,可以用来在任意位置获取全局命名空间。
示例:
scope = locals() # 当前命名空间 print(type(scope)) # <class 'dict'> # 下面两个打印效果相同 a = 20 print(a) # 20 print(scope['a']) # 20 # 向 scope 中添加一个 key-value # scope['c'] = 1000 # 向字典中添加 key-value 就相当于在全局中创建了一个变量(一般不建议这么做) # print(c) # 1000 def fn(): a = 10 scope = locals() # 在函数内部调用 locals() 会获取到函数的命名空间 print(type(scope)) # <class 'dict'> # scope['b'] = 90 # 可以通过 scope 来操作函数的命名空间,但是也是不建议这么做 # print(b) # globals() 函数可以用来在任意位置获取全局命名空间 # 函数外面无法获得函数的命名空间 global_scope = globals() print(global_scope['a']) # 20 # global_scope['a'] = 30 # 不建议这么做 # print(global_scope['a']) # 30 fn()
递归
递归
:递归是解决问题的一种方式,它和循环很像,它的整体思想是,将一个大问题分解为一个个的小问题,直到问题无法分解时,再去解决问题。
递归式函数的两个要件:
- 基线条件:问题可以被分解为的最小问题,当满足基线条件时,递归就不在执行了。
- 递归条件:将问题继续分解的条件。
示例:
# 10 的阶乘 n = 10 for i in range(1, 10): n *= i print(n) # 创建一个函数,可以用来求任意数的阶乘 def factorial(n): ''' 该函数用来求任意数的阶乘 参数: n 要求阶乘的数字 ''' # 创建一个变量,来保存结果 result = n for i in range(1, n): result *= i return result print(factorial(10)) # 递归简单理解就是自己去引用自己! # 递归式函数,在函数中自己调用自己! # 下面这个是无穷递归,如果这个函数被调用,程序的内存会溢出,效果类似于死循环 # def fn(): # fn() # fn() # 递归和循环类似,基本是可以互相代替的, # 循环编写起来比较容易,阅读起来稍难 # 递归编写起来难,但是方便阅读 def factorial(n): # 基线条件:判断 n 是否为 1,如果为 1,则此时不能再继续递归 if n == 1: # 1 的阶乘就是 1,直接返回 1 return 1 # 递归条件 return n * factorial((n - 1)) print(factorial(10)) # 创建一个函数 power,来为任意数字做幂运算 n ** i def power(n, i): ''' power() 用来为任意的数字做幂运算 参数: n 要做幂运算的数字 i 做幂运算的次数 ''' # 基线条件 if i == 1: # 求1次幂 return n # 递归条件 return n * power(n, i - 1) print(pow(3, 4)) # 创建一个函数,用来检查一个任意的字符串是否是回文字符串,如果是返回 True,否则返回 False # 回文字符串,字符串从前往后念和从后往前念是一样的 # abcba # abcdefgfedcba # 先检查第一个字符和最后一个字符是否一致,如果不一致则不是回文字符串 # 如果一致,则看剩余的部分是否是回文字符串 # 检查 abcdefgfedcba 是不是回文 # 检查 bcdefgfedcb 是不是回文 # 检查 cdefgfedc 是不是回文 # 检查 defgfed 是不是回文 # 检查 efgfe 是不是回文 # 检查 fgf 是不是回文 # 检查 g 是不是回文 def hui_wen(s): ''' 该函数用来检查指定的字符串是否回文字符串,如果是返回 True,否则返回 False 参数: s:就是要检查的字符串 ''' # 基线条件 if len(s) < 2: # 字符串的长度小于 2,则字符串一定是回文 return True elif s[0] != s[-1]: # 第一个字符和最后一个字符不相等,不是回文字符串 return False # 递归条件 return hui_wen(s[1:-1]) # def hui_wen(s): # ''' # 该函数用来检查指定的字符串是否回文字符串,如果是返回 True,否则返回 False # 参数: # s:就是要检查的字符串 # ''' # # 基线条件 # if len(s) < 2 : # # 字符串的长度小于 2,则字符串一定是回文 # return True # # 递归条件 # return s[0] == s[-1] and hui_wen(s[1:-1]) print(hui_wen('abba')) print(hui_wen('abcdefgfedcba'))
高阶函数
高阶函数
:接收函数作为参数,或者将函数作为返回值的函数是高阶函数。当我们使用一个函数作为参数时,实际上是将指定的代码传递进了目标函数。
在 Python 中,函数是一等对象。一等对象一般都会具有如下特点:
- 对象是在运行时创建的。
- 能赋值给变量或作为数据结构中的元素。
- 能作为参数传递。
- 能作为返回值返回。
高阶函数至少要符合以下两个特点中的一个:
- 能接收一个或多个函数作为参数。
- 能将函数作为返回值返回。
示例:
# 定义一个函数,功能:可以将指定列表中的所有的偶数,保存到一个新的列表中返回 # 待提取列表 l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] # 常规写法 def fn(lst): ''' fn() 函数可以将指定列表中的所有偶数提取出来,并保存到一个新列表中返回 参数: lst:指定的要筛选的列表 ''' new_list = [] for n in lst: if n % 2 == 0: new_list.append(n) return new_list print(fn(l)) # [2, 4, 6, 8, 10] # 高阶函数 # 功能 1:定义一个函数,用来检查一个任意的数字是否是偶数 def fn1(i): if i % 2 == 0: return True return False # 功能 2:定义一个函数,用来检查指定的数字是否大于 5 def fn2(i): if i > 5: return True return False # 功能 2:定义一个函数,用来检查一个任意的数字是否能被 3 整除 def fn3(i): return i % 3 == 0 # 多功能的高阶函数 def fn(func, lst): ''' fn() 函数可以将指定列表中的数据按指定函数要求提取出来,并保存到一个新列表中返回 参数: func:指定的提取要求 lst:指定的要筛选的列表 ''' new_list = [] for n in lst: if func(n): new_list.append(n) return new_list print(fn(fn1, l)) # 获取偶数:[2, 4, 6, 8, 10] print(fn(fn2, l)) # 获取大于 5 的数:[6, 7, 8, 9, 10] print(fn(fn3, l)) # 获取能被 3 整除的数:[3, 6, 9] # filter() 函数的功能,就如上面自定义的 fn() 函数 # filter() 可以从序列中过滤出符合条件的元素,保存到一个新的序列中 # 参数: # 1. 函数,根据该函数来过滤序列(可迭代的结构) # 2. 需要过滤的序列(可迭代的结构) # 返回值: # 过滤后的新序列(可迭代的结构) iterator = filter(fn1, l) # for n in iterator: # print(n) print(list(iterator)) # 返回的是一个可迭代的结构,需要转换成 list 才能直接打印出来数据 print(list(filter(fn2, l))) print(list(filter(fn3, l)))
匿名函数
匿名函数
:将一个或多个函数作为参数来接收。匿名函数一般都是作为参数使用,其他地方一般不会使用。
语法:lambda 参数列表 : 返回值
。
示例:
# 在前面的示例中,fn1 ~ fn3 是作为参数传递进 filter() 函数中 # 而 fn1 ~ fn3 实际上只有一个作用,就是作为 filter() 的参数 # filter() 调用完毕以后,fn1 ~ fn3 就已经没用 # 这种情况可以用匿名函数简化 # 匿名函数 lambda 函数表达式(语法糖) # lambda 函数表达式专门用来创建一些简单的函数,他是函数创建的又一种方式 # 语法:lambda 参数列表 : 返回值 # 匿名函数一般都是作为参数使用,其他地方一般不会使用 # 待提取列表 l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] # 常规写法 def fn5(a, b): return a + b print(fn5(10, 20)) # 常规写法 print((lambda a, b: a + b)(10, 20)) # lambad 表达式写法 fn6 = lambda a, b: a + b # 也可以将匿名函数赋值给一个变量,一般不会这么做 print(fn6(10, 20)) # filter() 函数中可以很方便的使用 lambda 表达式 # 此时,lambda 表达式只会使用一次,使用完后内存中自动回收 r = filter(lambda i: i % 2 == 0, l) # lambda i: i % 2 == 0 是匿名函数 print(list(r)) # [2, 4, 6, 8, 10] print(list(filter(lambda i: i > 5, l))) # [6, 7, 8, 9, 10],lambda i: i > 5 是匿名函数 # map() 函数可以对可迭代对象中的所有元素做指定的操作,然后将其添加到一个新的对象中返回 print(list(map(lambda i: i ** 2, l))) # 对列表中的每一个元素求平方,[1, 4, 9, 16, 25, 36, 49, 64, 81, 100] # sort() 函数用来对列表中的元素进行排序: # sotr() 只能排序列表 # 默认是直接比较列表中的元素的大小 # 在 sort() 可以接收一个关键字参数 key: # key 需要一个函数作为参数,当设置了函数作为参数 # 每次都会以列表中的一个元素作为参数来调用该函数 # 并且使用函数的返回值来比较元素的大小 l = ['bb', 'aaaa', 'c', 'ddddddddd', 'fff'] l.sort() print(l) # 默认比较:['aaaa', 'bb', 'c', 'ddddddddd', 'fff'] l = ['bb', 'aaaa', 'c', 'ddddddddd', 'fff'] l.sort(key=len) print(l) # 按长度比较:['c', 'bb', 'fff', 'aaaa', 'ddddddddd'] l = [2, 5, '1', 3, '6', '4'] l.sort(key=int) print(l) # 把每一个元素转换成整形后再比较:['1', 2, 3, '4', 5, '6'] l = [2, 5, '1', 3, '6', '4'] l.sort(key=str) print(l) # 把每一个元素转换成字符串后再比较:['1', 2, 3, '4', 5, '6'] # sorted() 函数和 sort() 的用法基本一致,但是 sorted() 可以对任意的序列进行排序 # 并且使用 sorted() 排序不会影响原来的对象,而是返回一个新对象 l = [2, 5, '1', 3, '6', '4'] # 排序列表 print('排序前:', l) # 排序前:[2, 5, '1', 3, '6', '4'] print('排序中:', sorted(l, key=int)) # 排序中:['1', 2, 3, '4', 5, '6'] print('排序后:', l) # 排序后:[2, 5, '1', 3, '6', '4'] l = '123765816742634781' # 排序字符串 print('排序前:', l) # 排序前:123765816742634781 print('排序中:', sorted(l, key=int)) # 排序中:['1', '1', '1', '2', '2', '3', '3', '4', '4', '5', '6', '6', '6', '7', '7', '7', '8', '8'] print('排序后:', l) # 排序后:123765816742634781
闭包
闭包
:是指在一个函数内部定义另一个函数,并且内部函数可以访问外部函数的局部变量。即使外部函数已经执行完毕,内部函数仍然可以访问外部函数的局部变量。
形成闭包的要件:
- 函数嵌套。
- 将内部函数作为返回值返回。
- 内部函数必须要使用到外部函数的变量。
示例:
# 将函数作为返回值返回,也是一种高阶函数 # 这种高阶函数我们也称为叫做闭包,通过闭包可以创建一些只有当前函数能访问的变量 # 可以将一些私有的数据藏到闭包中 def fn(): a = 10 # 函数内部再定义一个函数 def inner(): print('我是fn2', a) # 将内部函数 inner 作为返回值返回 return inner # r 是一个函数,是调用 fn() 后返回的函数 # 而且这个函数是在 fn() 内部定义,并不是全局函数 # 所以这个函数总是能访问到 fn() 函数内的变量 r = fn() print(r) # <function fn.<locals>.inner at 0x000001CEC1142430> r() # 我是fn2 10 # 求多个数的平均值 nums = [50, 30, 20, 10, 77] # 常规写法:sum() 用来求一个列表中所有元素的和 print(sum(nums) / len(nums)) # 37.4 # 如果 nums 中的数据是变化的,可以使用闭包 def make_averager(): # 创建一个列表,用来保存数值 nums = [] # 创建一个函数,用来计算平均值 def averager(n): # 将n添加到列表中 nums.append(n) # 求平均值 return sum(nums) / len(nums) return averager # 函数返回的是 make_averager() 中定义的 averager() 函数 # 并创建了一个 nums 列表,这个列表外界无法访问,只有 averager 对象可以访问 averager = make_averager() print(averager(10)) # 10/1=10 print(averager(20)) # (10+20)/2=15 print(averager(30)) # (10+20+30)/3=20 print(averager(40)) # (10+20+30+40)/4=25
装饰器
# 创建几个函数 def add(a, b): ''' 求任意两个数的和 ''' r = a + b return r def mul(a, b): ''' 求任意两个数的积 ''' r = a * b return r print(add(123, 456)) # 579 print(mul(10, 20)) # 200 # 现在,希望函数可以在计算前,打印开始计算,计算结束后打印计算完毕 # 我们可以直接通过修改函数中的代码来完成这个需求,但是会产生以下一些问题 # ① 如果要修改的函数过多,修改起来会比较麻烦 # ② 并且不方便后期的维护 # ③ 并且这样做会违反开闭原则(OCP) # 程序的设计,要求开发对程序的扩展,要关闭对程序的修改 # 我们希望在不修改原函数的情况下,来对函数进行扩展 def fn(): print('我是fn函数....') # 只需要根据现有的函数,来创建一个新的函数 def fn2(): print('函数开始执行~~~') fn() print('函数执行结束~~~') fn2() # 创建新函数,扩展 add() def new_add(a, b): print('计算开始~~~') r = add(a, b) print('计算结束~~~') return r r = new_add(111, 222) print(r) print('##############################################################') # 上边的方式,已经可以在不修改源代码的情况下对函数进行扩展了 # 但是,这种方式要求我们每扩展一个函数就要手动创建一个新的函数,实在是太麻烦了 # 为了解决这个问题,我们创建一个函数,让这个函数可以自动的帮助我们生产函数 def begin_end(old): ''' 用来对其他函数进行扩展,使其他函数可以在执行前打印开始执行,执行后打印执行结束 参数: old 要扩展的函数对象 ''' # 创建一个新函数,参数的个数是不确定的,使用*和** def new_function(*args, **kwargs): print('开始执行~~~~') # 调用被扩展的函数 result = old(*args, **kwargs) print('执行结束~~~~') # 返回函数的执行结果 return result # 返回新函数 return new_function f = begin_end(fn) # 包装 fn() f2 = begin_end(add) # 包装 add() f3 = begin_end(mul) # 包装 mul() r = f() print(r) r = f2(123, 456) print(r) r = f3(123, 456) print(r) print('##############################################################') # 像 begin_end() 这种函数我们就称它为装饰器 # 通过装饰器,可以在不修改原来函数的情况下来对函数进行扩展 # 在开发中,我们都是通过装饰器来扩展函数的功能的 # 在定义函数时,可以通过 @装饰器,来使用指定的装饰器,来装饰当前的函数 # 可以同时为一个函数指定多个装饰器,这样函数将会安装从内向外的顺序被装饰 def fn3(old): ''' 用来对其他函数进行扩展,使其他函数可以在执行前打印开始执行,执行后打印执行结束 参数: old 要扩展的函数对象 ''' # 创建一个新函数 # *args:old 函数中的位置参数(形如:'a, b'),全都存放其中 # **kwargs:old 函数中的字典参数(形如:'a=x, b=y'),全部存放其中 def new_function(*args, **kwargs): print('fn3装饰~开始执行~~~~') # 调用被扩展的函数 result = old(*args, **kwargs) print('fn3装饰~执行结束~~~~') # 返回函数的执行结果 return result # 返回新函数 return new_function @fn3 # 第一层:fn3 装饰 @begin_end # :第二层:begin_end 装饰 def say_hello(): print('大家好~~~') say_hello()
原文链接
https://github.com/ACatSmiling/zero-to-zero/blob/main/PythonLanguage/python.md
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步