文章目录
函数
函数也是对象,内存底层分析
在堆内存中创建好一个函数对象,可以反复多次调用
变量的作用域(全局变量和局部变量)
变量起作用的范围叫做变量的作用域,不同作用域内同名变量之间互不影响。
变量分为:全局变量、局部变量
全局变量:
1、在函数和类定义之外声明的变量。作用域为定义的模块,从定义位置开始直到模块结束。
2、全局变量降低了函数的通用性和可读性,应尽量避免全局变量的使用
3、全局变量一般做常量使用
4、函数内要改变全局变量的值,使用global声明一下
局部变量:
1、在函数体中(包含形式参数)声明的变量。
2、局部变量的引用比全局变量快,优先考虑使用。
3、如果局部变量和全局变量同名,则在函数内隐藏全局变量,只使用同名的局部变量。
栈帧局部变量在栈内存中的位置,调用一次就消失
a = 3
def text01():
b=4
print(b*10)
global a
a = 300
print(locals())
print(globals())
text01()
print()
效率测速
局部变量的查询和访问速度比全局变量快,优先考虑使用,尤其是在循环的时候。
在特别强调效率的地方或者循环次数较多的地方,可以通过全局变量转为局部变量提高运行速度。
import math
import time
def try01():
start= time.time()
for i in range(10000000):
math.sqrt(30)
end=time.time()
print('耗时{0}'.format((end-start)))
def try02():
b = math.sqrt
start= time.time()
for i in range(10000000):
b(30)
end=time.time()
print('耗时{0}'.format((end-start)))
try01()
try02()
运行结果:
耗时1.5187788009643555
耗时0.9154598712921143
参数的传递
参数传递的本质从实参到形参的赋值操作
,所有的赋值操作都是‘引用地址的赋值’。所以,python中参数的传递都是‘引用传递’而不是‘值传递’,具体操作分为两类:
1、对 可变对象 进行 写操作,直接作用于原对象本身
2、对 不可变对象 进行写操作,会产生一个新的对象空间,并用新的值填充这块空间(起到其他语言的值传递效果,而不是真正的值传递)
可变对象有:
字典、列表、集合、自定义的对象等
不可变的对象有:
数字、字符串、元组、function等
#传递可变对象
a = [10,20]
print(id(a))
print(a)
print('*********')
def try01(m):
print(id(m))
m.append(300)
print(id(m))
try01(a)
print(a)
传递可变对象的引用
传递参数是可变对象时,实际传递的还是对象的引用。函数体重不创建新的对象拷贝,而是直接修改所传递的对象。
【操作】参数传递:传递可变对象的引用
#传递可变对象
a = [10,20]
print(id(a))
print(a)
print('*********')
def try01(m):
print(id(m))
m.append(300)
print(id(m))
try01(a)
print(a)
运行结果:#在原地址对列表对象进行了修改
140666166894408
[10, 20]
*********
140666166894408
140666166894408
[10, 20, 300]
传递不可变对象的引用
传递参数是不可变对象,实际传递的还是对象,在‘赋值操作’时,由于不可变对象无法修改,系统会创建一个新对象
【操作】参数传递:传递不可变对象的引用
a=100
print('a:',id(a))
def f1(n):
print("n:",id(n))
n=n+200
print('n:',id(n))
f1(a)
print('a:',id(a))
运行结果:#在新的地址创建了对象
a: 4543894016
n: 4543894016
n: 140190875190128
a: 4543894016
浅拷贝和深拷贝
浅拷贝(copy):不拷贝子对象的内容,只是拷贝子对象的引用
深拷贝(deepcopy):会连子对象的内存也全部拷贝一份,对子对象的修改不会影响源对象
【操作】测试浅拷贝和深拷贝
#测试浅拷贝
import copy
def tryCopy():
a=[20,30,[3,5]]
b=copy.copy(a)
b.append(38)##注意append后为小括号
b[2].append(7)
print('a=',a)
print('b=',b)
运行结果:
a= [20, 30, [3, 5, 7]]
b= [20, 30, [3, 5, 7], 38]
tryCopy()
#测试深拷贝
import copy
def tryCopy():
a=[20,30,[3,5]]
b=copy.deepcopy(a)
b.append(38)
b[2].append(7)
print('a=',a)
print('b=',b)
tryCopy()
运行结果:#a完全没变
a= [20, 30, [3, 5]]
b= [20, 30, [3, 5, 7], 38]
传递不可变对象包含的子对象是可变的情况
传递不可变对象时,不可变对象里面包含的子对象是可变的。则方法内修改了这个可变对象,原对象也发生了变化。
a=(4,5,[2,9])
print('a:',id(a))
def try01(m):
print('m:',id(m))
m[2][0]=8
print(m)
print('m:',id(m))
try01(a)
print(a)
运行结果:#对原对象上不可变对象的可变子对象进行了修改
a: 140454794504808
m: 140454794504808
(4, 5, [8, 9])
m: 140454794504808
(4, 5, [8, 9])
参数的几种类型
位置参数
函数调用时,实参默认按照位置顺序传递,需要个数和形参匹配。按位置传递的参数叫做位置参数
def f1(a,b,c):
print(a,b,c)
f1(2,3,4)
f1(2,3)#报错,位置参数不匹配
运行结果:
2 3 4
Traceback (most recent call last):
File "/Users/melody/PycharmProjects/MyTest/parts.5/077.py", line 13, in <module>
f1(2,3)
TypeError: f1() missing 1 required positional argument: 'c'
默认值参数
可以为某些形参设置默认值,这样这些参数在传递时就是可选的。称为‘默认值参数’。默认值参数放在位置参数后
def f1(a,b,c=10,d=40):
print(a,b,c,d)
f1(2,3,4)
f1(2,3)
f1(2,5,67,23)
运行结果:
2 3 4 40
2 3 10 40
2 5 67 23
命名参数
按照形参的名称传递参数,称为‘命名参数’,也称关键字参数。
def f1(a,b,c):
print(a,b,c)
f1(2,3,4)
f1(a=9,c=5,b=67)
运行结果:
2 3 4
9 67 5
可变参数
可变参数指的是‘可变数量的参数’。分为两种情况:
1;*param(一个星号),将多个参数收集到一个‘元组’对象中
2;**param(2个星号),将多个参数收集到一个‘字典’对象中
def f1(a,b,*c):
print(a,b,c)
f1(2,3,4,30)
def f2(a,b,**c):
print(a,b,c)
f2(8,9,name='jining',age=19)
def f3(a,b,*c,**d):
print(a,b,c,d)
f3(39,40,3,2,name='jining',age=19)
运行结果:
2 3 (4, 30)
8 9 {'name': 'jining', 'age': 19}
39 40 (3, 2) {'name': 'jining', 'age': 19}
强制命名参数
在带星号的‘可变参数’后面加入新的参数,必须在调用的时候‘强制命名参数’
def f1(*a,b,c):
print(a,b,c)
f1(2,3,4)#不强制命名会报错
运行结果:
Traceback (most recent call last):
File "/Users/melody/PycharmProjects/MyTest/parts.5/077.py", line 41, in <module>
f1(2,3,4)
TypeError: f1() missing 2 required keyword-only arguments: 'b' and 'c'
def f1(*a,b,c):
print(a,b,c)
f1(2,b=3,c=4)
运行结果:
(2,) 3 4
lambda表达式和匿名函数
可以用来声明匿名函数。
是一种简单的、在同一行中定义函数的方法。
实际上为生成了一个函数对象
只允许包含一个表达式,不能包含复杂语句,该表达式的计算结果就是函数的返回值
lambda arg1,arg2,arg3... :<表达式>
arg1,arg2,arg3位函数的参数,<表达式>相当于函数体。运算结果是:表达式的运算结果
f=lambda a,b,c,:a+b+c
print(f)
print(f(4,6,9))
g=[lambda a:a*2,lambda b:b*9,lambda c:c*7]
print(g[0](4),g[1](8),g[2](3))
运行结果:
<function <lambda> at 0x7fec8129c378>
19
8 72 21
eval()函数
功能:将字符串str当成有效的表达式来求值并返回计算结果。
参数:
source:一个python表达式或函数compile()返回的代码对象
globals:可选。必须是dictionary
locals:可选。任意映射对象
s="print('abcde')"
eval(s)
a=10
b=30
c=eval("a+b")
print(c)
dict1= dict(a=100,b=200)
d=eval("a+b",dict1)
print(d)
运行结果:
abcde
40
300
递归函数
在函数体内直接或间接的调用自己
每个递归函数必须包含两个部分:
1、终止条件
表示递归什么时候结束。一般用于返回值,不再调用自己
2、递归步骤
把第n步的值和第n+1步相关联
由于会创建大量的函数对象,过量的消耗内存和运算能力。在处理大量数据时,谨慎使用
【操作】使用递归函数计算阶乘(factorial)
def factorial(n):
if n==1:
return 1
else:
return n*factorial(n-1)
result = factorial(5)
print(result)
运行结果:
120