Python基础之函数(七)


一、函数

函数的定义

使用def 语句定义一个函数a

  • 语法
 def function_name(parameters):
     # 函数体
     # 进行一些操作
     return result  # 可选的返回值
  • 说明

    1. def:用于声明一个函数,告诉 Python 这是一个函数的定义。
    2. function_name:函数名,一个有效的标识符,规则和变量名一致。
    3. parameters:形参,可以是0 ~ n 个,参数之间用逗号分隔。
    4. 函数体:定义函数执行的具体操作。
    5. return:指定函数的返回值,没有则返回None。
    6. return:之后的代码不会执行。
  • 示例

 def add(a, b):
     return a + b

二. 函数的调用

2.1 基本用法

  • 语法

    函数名(实际调用传递参数)
    
  • 说明

    函数调用是一个表达式,意思是他可以参与运算

  • 示例

 add(3, 5) 		# 直接调用
 add(3, 5) + 6 	#直接参与运算

2.2 调用传参

函数调用时传递参数的方式有多种,包括位置传参、关键词传参、多个参数解包、参数默认值等。

2.2.1 位置传参

最常见的传参方式,参数按定义的顺序依次传入函数。

示例

# 传入的实参顺序和形参顺序必须一致
def fn(x1,x2):
    print("x1:",x1)
    print("x2:",x2)
fn(1,2)
fn(2,1)

fn(x2 = 1,x1 = 2)
fn(1,x2 = 2) #可行,位置参数+关键字参数
#fn(x2 = 2,1) #报错,关键字参数+位置参数,不可行

def fn4(x1,x2,x3):
    print(x1,x2,x3)
fn4(1,2,x3=3) #可行
# fn4(x1=-1,2,x3=3) #报错

2.2.2 关键词传参

通过指定参数的名称来传值,无顺序限制,代码可读性较高。

示例:

def greet(name, age):
    print(f"Hello, {name}. You are {age} years old.")

greet(age=30, name="Alice")  

def fn(x1,x2):
    print("x1:",x1)
    print("x2:",x2)
fn(1,2)
fn(2,1)

fn(x2 = 1,x1 = 2)
fn(1,x2 = 2) #可行,位置参数+关键字参数

2.2.3 参数默认值

定义函数时可为某些参数指定默认值,如果不传参则使用默认值。默认值参数必须位于无默认值参数的后面。

示例:

# 参数默认值
def fn(a,b,c=100):
    print(a,b,c)
fn(1,2) # 1 2 100
fn(1,2,3) # 1 2 3
fn(1,None,c=3) # 1 None 3

# # 设计函数时候,默认值参数必须放在最后面,否则会报错
# def fn(a,b=100,c):
#     print(a,b,c)
# fn(1,2,3) #报错

2.2.4 可变位置参数和可变关键词参数

  • 使用 ∗ a r g s *args args 可让函数接受任意数量的位置参数。 ∗ a r g s *args args 会将多余的位置参数收集成一个元组。

示例

# 元组解包参数  *args
def fn(x1,x2,*x):
    print(x1,x2)
    print(x,type(x))
fn(10,20) # 10 20 ((),) <class 'tuple'>
fn(1,2,3,4,5,6) # 1 2 (3, 4, 5, 6) <class 'tuple'>

def fn2(*x):
    print(x,type(x))
fn2() # () <class 'tuple'>
fn2(1,2,3,4,5,6) # (1, 2, 3, 4, 5, 6) <class 'tuple'>
  • 使用 ∗ ∗ k w a r g s **kwargs kwargs 可以让函数接受任意数量的关键词参数。 ∗ ∗ k w a r g s **kwargs kwargs 会将多余的关键词参数收集成一个字典。

示例

# 可变关键词参数(字典解包参数)  **args
def fn(a, b, c, **args):
    print(a, b, c)
    print(args)

fn(10,20,30,name='zhangsan',age=18) 
# 10 20 30
#{'name': 'zhangsan', 'age': 18}
fn(1,2,3)
# 1 2 3 {}

2.2.5 多参数解包

Python 允许在调用函数时解包序列或字典,使其作为位置参数或关键词参数传递给函数。

2.2.5.1 解包位置参数

使用 ∗ * 解包

# 传入时解包
# *x,解包序列
def fn(a,b,c):
    print(a,b,c)
x = [1,2,3]
fn(x[0],x[1],x[2])
fn(*x) # *x就是解包x序列的元素,按顺序传入函数
# x2 = [4,5,6,7]
# fn(*x2) #报错,解包后数据多了,反之少了也不行

x3 =(4,5,6)
fn(*x3) #只要有下标的序列都可以解包
2.2.5.2 解包关键词参数

使用 ∗ ∗ ** 解包

# 传入时解包
# **解包字典
def fn(a,b,c):
    print(a,b,c)
s = {'a':1,'c':2,'b':3}
fn(a=s['a'],b=s['b'],c=s['c']) #1 3 2
fn(**s) #1 3 2
fn(**{'a':1,'b':2,'c':3}) #1 2 3

2.2.6 参数混合使用

在函数调用时,可以混合使用位置参数和关键词参数,但位置参数必须放在关键词参数前面。

示例

# (*)  参数
# (*) 本身不是一个参数,*只是一个占位符,它后面的参数必须是关键字参数
def fn(a, b, *, c = 10):
    print(a, b, c)
# fn(1, 2, 3) #报错,要求*后面的参数必须是关键字,也就是c要是关键字参数
fn(1, 2, c=3) #正确,1 2 3
fn(1, b=2, c=3) #正确,1 2 3
fn(1,2) #正确,1 2 10

三. 可变和不可变参数

在 Python 中,实参可以是可变类型或不可变类型。它们的区别主要体现在值传递引用传递的行为上。

3.1 不可变类型

不可变类型包括: i n t 、 f l o a t 、 s t r 、 t u p l e 、 f r o z e n s e t int、float、str、tuple、frozenset intfloatstrtuplefrozenset 等。

传递方式是值传递: 传递给函数的是该对象的值,函数内部修改该值不会影响外部变量的值。

示例:

def modify(x):
    print('修改之前:', x, id(x))
    x = 10  		# 修改了 x 的值,但不会影响外部变量
    print('修改之后:',x, id(x))

a = 5
modify(a)
print('原始数据:',a, id(a))

打印结果:

修改之前: 5 140722275297848
修改之后: 10 140722275298008
原始数据: 5 140722275297848

3.2 可变类型

可变类型包括: l i s t 、 d i c t 、 s e t list、dict、set listdictset 等。这些类型的对象可以在原地修改。

传递方式是引用传递 :传递给函数的是对象的引用(即内存地址),在函数内部修改该参数的内容会直接影响外部变量。

示例:

def modify(lst):
    print('修改之前:', lst, id(lst))
    lst.append(4)  # 修改了 lst 对象的内容
    print('修改之后:',lst, id(lst))

a = [1, 2, 3]
modify(a)
print('外部原始数据:',a, id(a)) 

打印结果:

修改之前: [1, 2, 3] 2123053058816
修改之后: [1, 2, 3, 4] 2123053058816
外部原始数据: [1, 2, 3, 4] 2123053058816

3.3 避免副作用

通过复制对象来避免可变类型副作用:

  • 列表:使用 lst.copy() 或切片 lst[:] 来创建一个副本。
  • 字典:使用 dict.copy()copy.deepcopy() 来进行深拷贝。
  • 元组:推荐用tuple 创建新的元组,虽然本身就是不可变。

示例:

def modify(lst):
    lst_copy = lst.copy()  # 创建副本
    lst_copy.append(4)      # 修改副本

a = [1, 2, 3]
modify(a)
print(a)  # 输出: [1, 2, 3] (外部列表不受影响)

# 通过设计函数的时候可以看出来 传入一个可以访问下标的数据容器 函数运行的时候才不会报错
def fn2(arr):
    arr[0]=100

x=[10,20,30]
fn2(x)
print(x)

# 解决方案:如果传入一个可变数据,函数内部想操作它,又不想改变外部传入的可变数据
def fn3(s):
    s = s.copy()
    s[0]= 1
    print(s)
x = [10,20]
fn3(x)
print(x) # [10,20]

四、匿名函数

匿名函数是没有名字的函数,通常用于需要一个简短的、临时的函数场景,它可以有任意数量的参数,但只能包含一个表达式,并返回该表达式的结果。

4.1 基本语法

lambda arguments: expression
  • arguments:一个或多个输入参数,可以是位置参数或关键词参数。
  • expression:一个单一的表达式,它的值将作为返回值返回。

4.2 匿名函数定义

# 定义一个 lambda 函数,接收两个参数 a 和 b,返回它们的和
add = lambda a, b: a + b

print(add(3, 5))  # 输出: 8

fn = lambda x, y: x + y
print(fn(1, 2))

fn2 = lambda x:"奇数" if x % 2==1 else "偶数"
print(fn2(2))

五、常见的官方内置函数

  • all():如果可迭代对象中的所有元素都为 True,返回 True,否则返回 False

  • sum():返回可迭代对象中所有元素的总和。

  • sorted():返回一个新列表,其中包含可迭代对象中的元素,按照升序排序。

示例

# 官方内置函数
x = [10,20,30,0]
re = all(x)
print(re)
re = any(x)
print(re)
print(sum(x))
print(sum(x)/len(x))

sorted(x) # 传入数据排序默认升序
print(x)

x = [-10,20,-30,0]
def fn(x):
    print(x)
    return x
re = sorted(x,key=fn)
print(re) #[-30, -10, 0, 20]

print(sorted(x,key=lambda x:x))

# abs() 函数,求绝对值
print(abs(-10))
  • reversed():返回一个反向迭代器。

  • callable():检查对象是否可以被调用(即是否是函数或方法)。

  • zip():将多个可迭代对象打包成一个元组,常用于并行遍历多个序列。

  • eval():将字符串作为有效的 Python 表达式来执行,并返回结果。

  • exec():执行存储在字符串中的 Python 代码。

  • globals()locals()``globals() :返回当前全局符号表(一个字典);locals() 返回当前局部符号表(也是字典)。

  • filter():从可迭代对象中过滤出符合条件的元素。

示例

# reversed(x)反转x函数,返回一个可迭代对象
x = [1, 2, 3, 40, 5]
re = reversed(x)
re = list(re)
print(re) # [5, 40, 3, 2, 1]

# callable(x)判断x是否可调用,返回布尔值,检测x标识符
fn = lambda x: x + 1
print(callable(fn)) # True
fn = 100 # 不可调用
print(callable(fn)) # False

# zip(x, y)函数,把x,y两个可迭代对象组合成一个可迭代对象
x = [1, 2, 3]
y = [4, 5, 6]
z = zip(x, y) # 允许x,y两个可迭代对象长度不一致
print(z) # <zip object at 0x0000012FCE354080>
z = list(z)
print(z) # [(1, 4), (2, 5), (3, 6)]

# eval(x)函数,执行x字符串,返回执行结果
x = 'print(1+1)'
eval(x) # 2

# exec(x)函数,执行x字符串
x = 'print(1+3)'
exec(x) # 4
exec('for i in range(10):print(i, end=" ")') # 0 1 2 3 4 5 6 7 8 9

# globals()函数,返回全局变量名字
print(globals())
# locals()函数,返回局部变量名字
def fn():
    x = 100
    y = 200
    print(locals())
fn() # {'x': 100, 'y': 200}

六. 高阶函数

高阶函数是指可以接受一个或多个函数作为参数,或者返回一个函数作为结果的函数。

6.1 常见高阶函数

比较常用,单独拎出来熟悉一下

6.1.1 map

map(function, iterable)

map函数:

  • 接受一个函数和一个可迭代对象
  • 将接受的函数应用到可迭代对象的每个元素上
  • 返回一个包含结果的迭代器

6.1.2 filter

filter(function, iterable)

filter函数:

  • 接受一个函数和一个可迭代对象
  • 用接受的函数来筛选出可迭代对象中满足条件的元素
  • 返回一个包含满足条件的元素的迭代器

6.1.3 reduce

reduce(function, iterable[, initializer])

reduce函数:

  • reduce函数接受一个函数和一个可迭代对象
  • 将接受的函数累积地应用到可迭代对象的元素上
  • 可选的 i n i t i a l i z e r initializer initializer 参数可以作为累积的初始值

示例

# filter(函数,可迭代对象),传入两个参数:函数和可迭代对象,筛选
x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
x2 = filter(lambda x: x % 2 == 0, x)
print(list(x2)) # [2, 4, 6, 8, 10]

# map(函数,可迭代对象),传入两个参数:函数和可迭代对象,每个数据都会被处理
x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
x2 = map(lambda x: x * 2, x)
print(list(x2))

# reduce(函数,可迭代对象),传入两个参数:函数和可迭代对象
from functools import reduce
x = [1,2,3,4,5]
def fn(x,y):
    print(x,y)
    return x + y
reduce(fn,x,0) # 0是初始值,可以是任何数字

七、变量作用域

变量的作用域(Scope)是指在程序中某个变量的有效范围,也就是在代码的哪个部分可以访问或修改该变量。作用域定义了变量的可见性和生命周期。

Python中的变量作用域遵循LEGB规则(Local, Enclosing, Global, Built-in),依次搜索变量的定义位置。

7.1 Local→局部作用域

  • 指函数或方法内部定义的变量。

  • 仅在函数内部有效,函数外部无法访问。

  • 在函数调用时被创建,在函数调用后自动销毁。

7.2 Enclosing→嵌套作用域

  • 指外层函数中的变量,在内层函数中可访问,但不可修改。

  • 当一个函数嵌套在另一个函数内部时,外层函数的变量属于Enclosing作用域。

  • nonlocal:将局部作用域中变量声明为外部嵌套函数作用域中的变量

7.3 Global→全局作用域

  • 指模块级别定义的变量,整个模块都可以访问。

  • 如果想在函数中修改全局变量,需要使用global关键字。

  • 示例:如何用一个变量来记录一个函数调用的次数

7.4 Built-in→内建作用域

  • 包含Python内建的函数、异常和常量,如print(), len(), int, Exception等。

  • 这些变量可以在任何地方使用。

  • 示例:

7.5 变量查找顺序

作用域遵循LEGB规则(Local, Enclosing, Global, Built-in)

7.6 示例

示例一

# 函数也是标识符 也是一种数据 也是跟变量一样的 关于作用域
a = 100
print(a) # 100

b = [10,202,30,lambda x:x+1]
print(b[0]) # 10
print(b[3](100)) # 101

示例二

# # 作用域-函数的调用
# # 函数调用在A作用域中,函数定义在B作用域中
# 函数无论在哪个作用域中调用执行,一定是在它定义的作用域下执行代码
a = 100
def fm():
    print(a)

def fn():
    a = 200
    fm()

fn() # 100

示例三

def t1():
    h = 100
    def t2():
        print(h)
    return t2

h = 200
re = t1()
re() # 100

示例四

a = 10
def fn():
    global a # a是全局变量的a
    print(a)
    a = 20 # 修改全局变量a的值
fn()
print(a) # 20

示例五

a = 1
def fn():
    a = 2
    def fn2():
        print(a) # 2
    fn2()
fn()
print(a) # 1

示例六

a = 10
def fn():
    a = 20
    def fn2():
        global a
        print(a) # 10
        a = 30
    fn2()
    print(a) # 20
fn()
print(a) # 30

示例七

a = 10
def fn():
    a = 10
    def fn2():
        nonlocal a # 非局部变量a
        print(a) # 10
        a = 20
    fn2()
    print(a) # 20
fn()
print(a) # 10

八. 函数内存分配

1、将函数的代码存储到代码区,函数体中的代码不执行。

2、调用函数时,在内存中开辟空间(栈帧),存储函数内部定义的变量。

3、函数调用后,栈帧立即被释放。

示例

# 函数内存分配

def func(a,b):
    a = 20
    b[0] = 50

a = 10
b = [30,10]
func(a,b)
print(a,b)

九、递归函数

递归函数通常由两个部分组成:

  1. 终止条件:用于确保递归能够停止,避免无限递归。
  2. 递归调用:函数在其内部调用自身,传递简化或减少的参数,以解决更小的子问题。

步骤分成三部:

  1. 定义递归函数:在函数体内调用函数自身。
  2. 设定终止条件:确保递归能终止。
  3. 逐步简化问题:通过传递较小的参数递归求解,直至终止条件满足。

以下是一些递归函数的例子:

def fn(a):
    if a >= 10:
        return 100
    else:
        return a*fn(a+1)
    
re = fn(1)
print(re) #36288000

一个例子和详细解释:

def fn(a):
    if a>=5:
        return 5
    else:
        return fn(a+1)
a=1 
re=fn(a) 
print(re)
伪代码="""
伪代码 主要用于分析程序的执行过程
假设 大括号就是作用域

global->scoped{
    fn函数
    a=1
    re=fn函数执行后的结果
    fn(a)==>执行产生作用域
    fn(1)->scoped{
          a=1
          if a>=5:
              return 5
          else:
              return fn(a+1)->结果fn(2)
      }
    fn(2)->scoped{
        a=2
        if a>=5:
              return 5
        else:
              return fn(a+1)->结果fn(3)
    }
    fn(3)->scoped{
       a=3
       if a>=5:
              return 5
       else:
              return fn(a+1)->结果fn(4)
    }
    fn(4)->scoped{
        a=4
        if a>=5:
              return 5
        else:
              return fn(a+1)->结果fn(5)
    }
    fn(5)->scoped{
        a=5
        if a>=5:
              return 5-->结果5
        else:
              return fn(a+1)
    
    }

}
"""

十、思考题

#今天的作业: 测试题: x2不要求是迭代器 是列表就可以了 实现myfilter 和 mymap myreduce

```# myfilter 函数
def myfilter(fn, list):
    re = []
    for el in list:
        if fn(el):
            re.append(el)
    return re


# mymap 函数
def mymap(fn, list):
    result = []
    for el in list:
        result.append(fn(el))
    return result


# myreduce 函数
def myreduce(fn,list):
    re=list[0]
    for i in list[1:]:
        re=fn(re,i)
    return re

x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(myfilter(lambda el: el % 2 == 1, x))  # 输出:[1, 3, 5, 7, 9]

print(mymap(lambda el: el ** 2, x))  # 输出:[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

x=[1,2,3,3,4]
def fn(a,b):
 print(a,b,)
 return a+b
re=myreduce(fn,x)
print(re)
  
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐