文章目录
- 前言
- 一、函数内存分析
- 二、全局变量&局部变量
- 1、输出局部变量和全局变量
- 三、参数的传递内存分析
- 1、传递可变对象的引用
- 2、传递不可变对象的引用
- 3、传递不可变对象包含可变的子对象
- 四、浅拷贝&深拷贝内存分析
- 五、参数的类型
- 1、位置参数
- 2、默认值参数
- 3、命名参数
- 4、可变参数
- 5、强制命名参数
- 六、lambda 表达式
- 七、eval() 函数
- 八、递归函数
- 1、递归函数调用内存分析
- 2、阶乘计算案例
前言
本文为7月1日Python基础学习笔记,分为八个章节:
- 函数内存分析;
- 全局变量&局部变量;
- 参数的传递内存分析:传递可变对象、传递不可变对象、传递包含可变子对象的不可变对象;
- 参数的类型:位置参数、默认值参数、命名参数、可变参数、强制命名参数;
- lambda 表达式;
- eval() 函数;
- 递归函数:递归函数调用内存分析以及阶乘计算案例。
一、函数内存分析
函数也是对象。
def print_star(n):
print('*' * n)
print(print_star)
>>> <function print_star at 0x00000174D2010670>
print(id(print_star))
>>> 1601251116656
print_star(3)
>>> ***
c = print_star
c(3)
>>> ***
print(id(c))
>>> 1601251116656
如图所示:
- 执行 “c=print_star” 后,c 和 print_star 指向同一个函数对象。
二、全局变量&局部变量
- 全局变量: 在函数和类定义之外声明的变量。作用域为定义的模块,从定义位置开始直到模块结束。
- 作用域:从定义位置开始直到模块结束;
- 降低了函数的通用性和可读性,尽量避免使用;
- 一般做常量使用;
- 使用 global 可声明以改变局部变量。
- 局部变量:
- 在函数体中(包含形式参数)声明;
- 局部变量的引用比全局变量快,优先考虑使用;
- 如果局部变量和全局变量同名,则在函数内隐藏全局变量,只使用同名的局部变量。
1、输出局部变量和全局变量
a = 100
def f1(a, b, c):
print(a, b, c)
print(locals()) # 打印输出的局部变量
print('-' * 20) # 分隔符
print(globals()) # 打印输出的全局变量
f1(2, 3, 4)
>>> 2 3 4
>>> {'a': 2, 'b': 3, 'c': 4}
>>> --------------------
>>> {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'print_star': <function print_star at 0x00000174D2010670>, 'c': <function print_star at 0x00000174D2010670>, 'a': 100, 'f1': <function f1 at 0x00000174D20137F0>}
三、参数的传递内存分析
从实参到形参的赋值操作。 Python 中参数的传递都是引用传递,不是值传递。 分为:
- 对可变对象进行“写操作”,直接作用于原对象本身;
- 对不可变对象进行“写操作”,会产生新的对象空间。
可变对象:
- 字典;
- 列表;
- 集合;
- 自定义的对象等。
不可变对象:
- 数字;
- 字符串;
- 元组;
- function 等。
1、传递可变对象的引用
传递参数是可变对象,实际传递的还是对象的引用。在函数体中不创建新的对象拷贝,而是可以直接修改所传递的对象。
b = [10, 20]
def f2(m):
print("m: ", id(m))
m.append(30)
f2(b)
print("b: ", id(b))
print(b)
>>> m: 2687028557440
>>> b: 2687028557440
>>> [10, 20, 30]
2、传递不可变对象的引用
传递参数是不可变对象,在”赋值操作”时,由于不可变对象无法修改,系统会新创建一个对象。
a = 100
def f1(n):
print("n: ", id(n)) # 打印 a 刚传递进来时的地址
n = n + 200 # a 是不可变对象,创建新的对象 n
print("n: ", id(n)) # 打印新对象的地址
print(n)
f1(a)
print("a: ", id(a)) # 打印全局变量 a 的地址
>>> n: 2291565858128
>>> n: 2291565870224
>>> 300
>>> a: 2291565858128
3、传递不可变对象包含可变的子对象
修改了这个可变对象,源对象也发生了变化。
# 传递不可变对象时,不可变对象里若包含可变的子对象
# 修改该子对象,源对象也会被修改
a = (10, 20, [5, 6])
print("a:", id(a))
def test01(m):
print("m: ", id(m))
m[2][0] = 888
print(m)
print("m: ", id(m))
test01(a)
print(a)
>>> a: 1688105036928
>>> m: 1688105036928
>>> (10, 20, [888, 6])
>>> m: 1688105036928
>>> (10, 20, [888, 6])
四、浅拷贝&深拷贝内存分析
- 浅拷贝: 不拷贝子对象的内容,只是拷贝子对象的引用;
import copy
def testCopy():
'''测试浅拷贝'''
a = [10, 20, [5, 6]]
b = copy.copy(a)
print("a:", a)
print("b:", b)
b.append(30)
b[2].append(7)
print("浅拷贝…………")
print("a:", a)
print("b:", b)
testCopy()
>>> a: [10, 20, [5, 6]]
>>> b: [10, 20, [5, 6]]
>>> 浅拷贝…………
>>> a: [10, 20, [5, 6, 7]]
>>> b: [10, 20, [5, 6, 7], 30]
- 深拷贝: 连子对象的内存也全部拷贝一份, 对子对象的修改不会影响源对象。
import copy
def testDeepCopy():
'''测试深拷贝'''
a = [10, 20, [5, 6]]
b = copy.deepcopy(a)
print("a:", a)
print("b:", b)
b.append(30)
b[2].append(7)
print("深拷贝…………")
print("a:", a)
print("b:", b)
testDeepCopy()
>>> a: [10, 20, [5, 6]]
>>> b: [10, 20, [5, 6]]
>>> 深拷贝…………
>>> a: [10, 20, [5, 6]]
>>> b: [10, 20, [5, 6, 7], 30]
五、参数的类型
1、位置参数
实参默认按位置顺序传递,需要个数和形参匹配。
def f1(a, b, c):
print(a, b, c)
f1(2, 3, 4)
>>> 2 3 4
2、默认值参数
为某些参数设置默认值, 这样这些参数在传递时就是可选的。默认值参数放到位置参数后面。
def f1(a, b, c=10, d=20):
print(a, b, c, d)
f1(8, 9, 19)
>>> 8 9 19 20
3、命名参数
按照形参的名称传递参数。
def f1(a, b, c):
print(a, b, c)
f1(c=10, a=20, b=30)
>>> 20 30 10
4、可变参数
- *param(一个星号),将多个参数收集到一个元组对象中;
- **param(两个星号),将多个参数收集到一个字典对象中。
def f3(a, b, *c, **d):
print(a, b, c, d)
f3(8, 9, 20, 30, name='xiaoli', age=18)
>>> 8 9 (20, 30) {'name': 'xiaoli', 'age': 18}
5、强制命名参数
在带星号的“可变参数”后面增加新的参数。
def f1(*a, b, c):
print(a, b, c)
f1(2, b=3, c=4)
>>> (2,) 3 4
六、lambda 表达式
lambda 表达式可用来声明匿名函数,是一种在同一行中定义函数的方法。
lambda 表达式只允许包含一个表达式, 不能包含复杂语句,该表达式的计算结果就是函数的返回值。
f = lambda a, b, c: a+b+c
print(f)
>>> <function <lambda> at 0x00000174D2013760>
f(2, 3, 4)
>>> 9
七、eval() 函数
将字符串 str 当成有效的表达式来求值并返回计算结果。
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
八、递归函数
1、递归函数调用内存分析
每个递归函数必须包含:
- 终止条件: 表示递归什么时候结束。一般用于返回值,不再调用自己;
- 递归步骤: 把第 n 步的值和第 n-1 步相关联。
# 递归函数的内存使用
def test01(n):
print("test01: ", n)
if n==0:
print("over")
else:
test01(n-1)
print("test***", n)
test01(4)
>>> test01: 4
test01: 3
test01: 2
test01: 1
test01: 0
over
test*** 0
test*** 1
test*** 2
test*** 3
test*** 4
2、阶乘计算案例
- 使用递归函数计算阶乘(factorial)。
def factorial(n):
if n==1:
return 1
else:
return n * factorial(n-1)
result = factorial(5)
print(result)
>>> 120