《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('孙悟空') # 欢迎 孙悟空 光临

参数的传递方式

函数的传参方式有多种:

  1. 默认值参数:定义形参时,可以为形参指定默认值。指定了默认值以后,如果用户传递了参数,则默认值没有任何作用;如果用户没有传递参数,则默认值就会生效。
  2. 位置参数:即将对应位置的实参复制给对应位置的形参
  3. 关键字参数:可以不按照形参定义的顺序去传递,而直接根据参数名去传递参数
    • 位置参数和关键字参数可以混合使用,混合使用时,必须将位置参数写到前面。
  4. 函数在调用时,解析器不会检查实参的类型,实参可以传递任意类型的对象
  5. 在函数中对形参进行重新赋值,不会影响其他的变量。
  6. 如果形参指向的是一个对象,当我们通过形参去修改对象时,会影响到所有指向该对象的变量。

示例:

# 定义函数,并为形参指定默认值
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 中一共有两种作用域:

  1. 全局作用域:全局作用域在程序执行时创建,在程序执行结束时销毁。
    • 所有函数以外的区域都是全局作用域。
    • 在全局作用域中定义的变量,都属于全局变量,全局变量可以在程序的任意位置被访问。
  2. 函数作用域:函数作用域在函数调用时创建,在调用结束时销毁。
    • 函数每调用一次就会产生一个新的函数作用域。
    • 在函数作用域中定义的变量,都是局部变量,它只能在函数内部被访问。
    • 如果希望在函数内部修改全局变量,则需要使用 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()

递归

递归:递归是解决问题的一种方式,它和循环很像,它的整体思想是,将一个大问题分解为一个个的小问题,直到问题无法分解时,再去解决问题。

递归式函数的两个要件:

  1. 基线条件:问题可以被分解为的最小问题,当满足基线条件时,递归就不在执行了。
  2. 递归条件:将问题继续分解的条件。

示例:

# 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

闭包

闭包:是指在一个函数内部定义另一个函数,并且内部函数可以访问外部函数的局部变量。即使外部函数已经执行完毕,内部函数仍然可以访问外部函数的局部变量。

形成闭包的要件:

  1. 函数嵌套。
  2. 将内部函数作为返回值返回。
  3. 内部函数必须要使用到外部函数的变量。

示例:

# 将函数作为返回值返回,也是一种高阶函数
# 这种高阶函数我们也称为叫做闭包,通过闭包可以创建一些只有当前函数能访问的变量
# 可以将一些私有的数据藏到闭包中
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

posted @   ACatSmiling  阅读(43)  评论(0编辑  收藏  举报
努力加载评论中...
点击右上角即可分享
微信分享提示