Yunyuzuiluo

Python 百战 6 函数

函数的基本概念_内存分析_函数分类_定义和调用

function

Python函数的分类

内置函数
标准库函数
第三方库函数
用户自定义函数

函数的定义和调用

def 函数名 ([参数列表]) :
'''文档字符串'''
函数体/若干语句

def add (a, b):
print(a,b)
return a + b
print(add(1, 2))

形参和实参

def printMax(a,b): #形参,不用定义类型
if a>b:
print(a,'较大值')
return a
else:
print(b,'较大值')
return b

printMax(10,20) #10,20是实参,要和形参一一对应
printMax(30,5)

函数内存底层分析

def print_star(n):
print(""n)

print(print_star)
print(id(print_star))

c = print_star #别名
c(3) #和print_star(3)一样

变量的作用域(全局变量和局部变量)

a = 100 #全局变量
def f1():
global a #如果要在函数内改变全局变量的值,增加global关键字声明
print(a) #打印全局变量a的值
a = 300
f1()
print(a)

参数的传递

Python中参数的传递都是“引用传递”,不是“值传递”
可变对象有:
字典、列表、集合、自定义的对象等
不可变对象有:
数字、字符串、元组、function等

传递可变对象的引用

传递参数是可变对象(例如:列表、字典、自定义的其他可变对象等),实际传递的还是对象的引用。在函数体中不创建新的对象拷贝,而是可以直接修改所传递的对象。19

b = [10,20]
def f2(m):
print("m:",id(m)) #b和m是同一个对象
m.append(30) #由于m是可变对象,不创建对象拷贝,直接修改这个对象
f2(b)
print("b:",id(b))
print(b)

执行结果:
m: 45765960
b: 45765960
[10, 20, 30]

传递不可变对象的引用

传递参数是不可变对象(例如: int 、 float 、字符串、元组、布尔值),实际传递的还是对象的引用。在”赋值操作”时,由于不可变对象无法修改,系统会新创建一个对象。

a = 100
def f1(n):
print("n:",id(n)) #传递进来的是a对象的地址
n = n+200 #由于a是不可变对象,因此创建新的对象n
print("n:",id(n)) #n已经变成了新的对象
print(n)
f1(a)
print("a:",id(a))

浅拷贝和深拷贝

内置函数: copy (浅拷贝)、 deepcopy (深拷贝)。

浅拷贝 (copy.copy())

只拷贝最外层对象,对于内嵌的对象(如列表中的子列表)只拷贝引用
修改拷贝后的对象:
添加/删除外层元素:不会影响原对象
修改内层可变对象(如子列表):会影响原对象
示例中 b.append(30) 只影响b,但 b[2].append(7) 影响了a和b

深拷贝 (copy.deepcopy())

递归拷贝所有层级的对象,完全独立的新对象
修改拷贝后的对象:
任何修改都不会影响原对象
包括外层和内层的所有修改
示例中所有对b的修改都不会影响a

import copy

def testCopy():
"""测试浅拷贝(shallow copy)的效果"""
a = [10, 20, [5, 6]] # 原始列表,包含两个整数和一个子列表
b = copy.copy(a) # 创建a的浅拷贝

print("a", a)         # 输出原始列表a
print("b", b)         # 输出拷贝列表b

b.append(30)         # 修改b,添加新元素30
b[2].append(7)       # 修改b的子列表,添加元素7

print("浅拷贝......")
print("a", a)        # 再次输出a,观察变化
print("b", b)        # 输出b,观察变化

def testDeepCopy():
"""测试深拷贝(deep copy)的效果"""
a = [10, 20, [5, 6]] # 原始列表,包含两个整数和一个子列表
b = copy.deepcopy(a) # 创建a的深拷贝

print("a", a)         # 输出原始列表a
print("b", b)         # 输出拷贝列表b

b.append(30)         # 修改b,添加新元素30
b[2].append(7)       # 修改b的子列表,添加元素7

print("深拷贝......")
print("a", a)        # 再次输出a,观察变化
print("b", b)        # 输出b,观察变化

执行测试函数
testCopy()
print("*************")
testDeepCopy()

结果:
a [10, 20, [5, 6]]
b [10, 20, [5, 6]]
浅拷贝......
a [10, 20, [5, 6, 7]] # 注意子列表被修改了
b [10, 20, [5, 6, 7], 30]


a [10, 20, [5, 6]]
b [10, 20, [5, 6]]
深拷贝......
a [10, 20, [5, 6]] # 原始列表完全不受影响
b [10, 20, [5, 6, 7], 30]

传递不可变对象包含的子对象是可变的情况

a = (10, 20, [5, 6])
print("a:", id(a)) # 输出:a: 140704485362496 (示例地址,实际运行会变)

def test01(m):
print("m:", id(m)) # 输出:m: 140704485362496 (与a相同地址)
m[2][0] = 888 # 修改元组中的列表元素
print(m) # 输出:(10, 20, [888, 6])
print("m:", id(m)) # 输出:m: 140704485362496 (地址不变)

test01(a)
print(a) # 输出:(10, 20, [888, 6]) (原始元组的列表已被修改)

位置参数

def f1(a,b,c):
print(a,b,c)
f1(2,3,4)
f1(2,3) #报错,位置参数不匹配

def f1(a,b,c=10,d=20): #默认值参数必须位于普通
位置参数后面
print(a,b,c,d)
f1(8,9) #8,9,10,20
f1(8,9,19) #8,9,19,20
f1(8,9,19,29) #8,9,19,29
f1(c=10,a=20,b=30) #命名参数,20,30,10,20

可变参数

def f1(a,b,c):#一个‘’把多余的给元组
print(a,b,c)
f1(1,2,3,4)

def f2(a,b,**c):#两个‘*’把多余的给字典
print(a,b,c)
f2(8,9,name='gaoqi',age=18)

def f3(a,b,*c,**d):
print(a,b,c,d)
f3(8,9,20,30,name='gaoqi',age=18)

输出结果:
8 9 (19, 20)
8 9 {'name': 'gaoqi', 'age': 18}
8 9 (20, 30) {'name': 'gaoqi', 'age': 18}

强制命名参数

def f1(*a,b,c):
print(a,b,c)
f1(2,3,4) #会报错。由于a是可变参数,将2,3,4全部收集。造成b和c没有赋值。
f1(2,b=3,c=4)

lambda表达式和匿名函数

lambda 表达式可以用来声明匿名函数。 lambda 函数是一种简单的、在同一行中定义函数的方法。 lambda 函数实际生成了一个函数对象。
lambda 表达式只允许包含一个表达式,不能包含复杂语句,该表达式的计算结果就是函数的返回值。
lambda 表达式的基本语法如下
lambda arg1,arg2,arg3... : <表达式>

f = lambda a,b,c:a+b+c
print(f) #<function at 0x0000000002BB8620>
print(f(2,3,4)) #9
g = [lambda a:a2,lambda b:b3,lambda c:c*4]
print(g0,g1,g2) #12,21,32

eval()函数

eval函数 会将字符串当做语句来执行,因此会被注入安全隐患。比如:字符串中含有删除文件的语句。那就麻烦大了。因此,使用时候,要慎重!!!
功能:将字符串 str 当成有效的表达式来求值并返回计算结果。
语法: eval(source[, globals[, locals]]) -> value
source :一个Python表达式或函数 compile() 返回的代码对象1
globals :可选。必须是 dictionary2
locals :可选。任意映射对象335

s = "print('abcde')"
eval(s) #abcde
a = 10
b = 20
c = eval("a+b")
print(c) #30
dict1 = dict(a=100,b=200)
d = eval("a+b",dict1)
print(d) #300

递归函数

def countdown(n):
"""递归倒计时"""
if n <= 0:
print("发射!")
else:
print(n)
countdown(n - 1) #递归关键点

countdown(3)

嵌套函数(内部函数)

一般在什么情况下使用嵌套函数?
1 封装 - 数据隐藏外部无法访问“嵌套函数”。
2 贯彻 DRY(Don’t Repeat Yourself) 原则
3 嵌套函数,可以让我们在函数内部避免重复代码。
4 闭包(后面会讲解)
def outer():
print('outer running...')
def inner():
print('inner running...')
inner()
outer()

nonlocal关键字

nonlocal 用来在内部函数中,声明外层的局部变量。
global 函数内声明全局变量,然后才使用全局变量

a = 100 # 定义全局变量a

def outer():
b = 10 # 定义outer的局部变量b

def inner():
    nonlocal b  # 声明b不是inner的局部变量,而是外部函数outer的b
    print("inner b:", b)  # 输出outer的b值 -> inner b: 10
    b = 20  # 修改outer的b值
    
    global a  # 声明要修改的是全局变量a
    a = 1000  # 修改全局变量a的值

inner()  # 调用inner函数

print("outer b:", b)  # 输出outer的b值(已被inner修改) -> outer b: 20

outer() # 调用outer函数

print("a:", a) # 输出全局变量a的值(已被inner修改) -> a: 1000

LEGB规则

LEGB规则是Python查找变量名的顺序规则,代表:
Local(局部作用域)→ Enclosing(嵌套函数外层)→ Global(全局)→ Built-in(内置)

关键点总结:
查找顺序:局部 → 外层 → 全局 → 内置
赋值行为:默认在Local层创建新变量
作用域穿透:
global:直接操作Global层
nonlocal:操作最近的Enclosing层
找不到变量:最终会查找Built-in层,如果还没有则报NameError

posted on 2025-04-16 20:25  刘晋宇  阅读(8)  评论(0)    收藏  举报

导航