Python基础(6)
函数
函数用法和底层分析
函数是可重用的代码块,一个程序由一个个任务组成,函数就代表一个任务或者一个功能。
Python中函数分为以下几类:
核心要点:
内存底层分析:
def all():
print('Hello')
all()
a = all
a()
#结果为:
Hello
Hello
函数定义时已经建立好了一个对象放在堆内存中,函数名放在栈内存中,函数名与函数对象的地址放在一起,小括号()代表调用的意思。
因此all()表示调用了名为all的函数,而a=all则是把all这一函数名和绑定的地址赋值给了a,但并未调用,所以a加上()以后才实现了真正的调用,实现了函数功能。
形参和实参
def printMax(a,b):
if a>b:
print(a)
else:
print(b)
printMax(3,6)
#结果为:
6
其中a、b为形参,只在定义函数时使用;而3和6为实参,是使用函数时传递的参数。
文档字符串(函数的注释)
def printMax(a,b):
"""
:param a:输入数字
:param b:输入数字
:return:返回最大值
打印最大值
"""
if a>b:
print(a)
else:
print(b)
printMax(3,6)
其中三引号囊括的内容就是函数注释。
可以通过以下格式打印函数注释:
def printMax(a,b):
"""
:param a:输入数字
:param b:输入数字
:return:返回最大值
打印最大值
"""
if a>b:
print(a)
else:
print(b)
print(printMax.__doc__)
#结果为:
:param a:输入数字
:param b:输入数字
:return:返回最大值
打印最大值
返回值
def all(a,b):
return a+b
c = all(3,7)
print(c)
#结果为:
10
变量的作用域(全局变量和局部变量)
a = 2
def test():
b = 1
print(b*5)
这样一段代码中,test函数名绑定了在堆中的函数对象的地址,a绑定了在堆内存中的对象2的地址,但b是一个栈帧,绑定了堆内存中对象1的地址,但只在调用时存在,一旦不使用,则栈帧消失,具体如下图所示:
参数的传递
函数的参数传递本质上是:从实参到形参的赋值操作。其中所有的赋值操作都是“引用的赋值”。
传递可变对象的引用
对于可变对象(字典、列表、集合等),即将实参对象的id赋值给形参,形参绑定在了同一对象上。
a = [1,6]
print(id(a))
def test(m):
print(id(m)) #传进来的m跟a绑定的是一个对象
m.append(8) # 因为是可变对象所以直接在原对象里进行更改
print(id(m))
test(a)
#结果为:
2773035545992
2773035545992
2773035545992
传递不可变对象的引用
传递参数是不可变对象(int、float、字符串、元组、布尔值)时,实际传递的还是对象的引用,在赋值操作时,由于对象不可改变,系统会创建一个新对象。
这种情况下是浅拷贝!!!
a = 2
print(id(a))
def test(m):
print(id(m))
m = m+1 # 由于m绑定的是不可变对象,所以系统创建了新对象
print(id(m))
test(a)
#结果为:
1972941024
1972941024
1972941056
浅拷贝和深拷贝
浅拷贝: 只拷贝表层对象,不拷贝子对象的内容,只拷贝子对象的引用
a = [1,2,3,[1,2,3,[5,6]]]
b = a
c = a.copy()
图解如下:
对b进行操作:
b.append(8)
print(a)
print(b)
b[3].append(9)
print(a)
print(b)
#结果为:
[1, 2, 3, [1, 2, 3, [5, 6]], 8]
[1, 2, 3, [1, 2, 3, [5, 6]], 8]
[1, 2, 3, [1, 2, 3, [5, 6], 9], 8]
[1, 2, 3, [1, 2, 3, [5, 6], 9], 8]
由此可看出,对b进行的操作在a中是一样的
对c进行操作:
c.append(8)
print(a)
print(c)
#结果为:
[1, 2, 3, [1, 2, 3, [5, 6]]]
[1, 2, 3, [1, 2, 3, [5, 6]], 8]
c[3].append(9)
print(a)
print(c)
#结果为:
[1, 2, 3, [1, 2, 3, [5, 6], 9]]
[1, 2, 3, [1, 2, 3, [5, 6], 9]]
由此可得,浅拷贝时对表层的改变不会在原对象身上体现,但在子对象中的改变却可以在原对象身上显现,因为浅拷贝只拷贝了表层,指向的子对象是原对象的子对象。(结合上图理解)
深拷贝: 会连子对象的内存也全部拷贝一份,对子对象的修改不会影响源对象
使用深拷贝时要用copy包
import copy
a = [1,2,3,[1,2,3,[5,6]]]
c = copy.deepcopy(a)
对表层做出改变:
c.append(9)
print(a)
print(c)
#结果为:
[1, 2, 3, [1, 2, 3, [5, 6]]]
[1, 2, 3, [1, 2, 3, [5, 6]], 9]
对第二层做出改变:
c[3].append(8)
print(a)
print(c)
#结果是:
[1, 2, 3, [1, 2, 3, [5, 6]]]
[1, 2, 3, [1, 2, 3, [5, 6], 8]]
对第三层做出改变:
c[3][3].append(7)
print(a)
print(c)
#结果是:
[1, 2, 3, [1, 2, 3, [5, 6]]]
[1, 2, 3, [1, 2, 3, [5, 6, 7]]]
由此可得,深拷贝做任何改变都不对原对象造成更改
不可变对象含可变子对象
不可变对象传递时是浅拷贝
由于只能拷贝表层,所以子对象改变时并不会改变m的地址。
a = (1,2,3,[6,8])
def test(m):
print(id(m))
m[3].append(9)
print(m)
print(id(m))
print(a)
test(a)
#结果为:
(1, 2, 3, [6, 8])
2212168948056
(1, 2, 3, [6, 8, 9])
2212168948056
但如果在表层进行了改变,地址变化:
a = (1,2,3,[6,8])
def test(m):
print(id(m))
m = m + (9,)
print(m)
print(id(m))
print(a)
test(a)
#结果为:
(1, 2, 3, [6, 8])
1504475634008
(1, 2, 3, [6, 8], 9)
1504472068608
参数的类型
1、位置参数:
2、默认值参数:
3、命名参数:
4、可变参数:
def test(a,b,*c,**d):
print(a)
print(b)
print(c)
print(d)
test(1,5,6,2,name = 'zzz',age = 18)
#结果为:
1
5
(6, 2)
{'name': 'zzz', 'age': 18}
5、强制命名参数
def test(*a,b,c):
print(a,b,c)
# test(1,2,3) #这样错误,因为1、2、3都被认为是a的参数的值
test(1,b=3,c=6)
#结果为:
(1,) 3 6
lambda表达式和匿名函数
f = lambda x,y,z:x+y+z
print(f(1,6,8))
#结果为:
15
g = [lambda x:x*2 ,lambda y:y*3]
print(g[0](3))
print(g[1](1))
#结果为:
6
3
eval()函数用法
a = 10
b = 20
dict_1 = dict(a=100,b=120)
d = eval('a+b')
e = eval('a+b',dict_1)
print(d)
print(e)
#结果为:
30
220
递归函数
内存分析
例子一:
def test1():
print('test1')
test2()
def test2():
print('test2')
test1()
#结果为:
test1
test2
调用test1时创建一个栈帧,还未消失的时候建立了第二个栈帧test2,test2运行完毕后test2栈帧消失,继续执行test1
例子二:
def test(n):
print(n)
if n==0:
print('over')
else:
test(n-1)
test(3)
#结果为:
3
2
1
0
over
当n=3时,创建一个栈帧test(3),经过判断语句后,在test(3)栈帧不消失的情况下再建立一个test(2)栈帧,以此类推
例子三:
def test(n):
print(n)
if n==0:
print('over')
else:
test(n-1)
print('****',n)
test(2)
#结果为:
2
1
0
over
**** 0
**** 1
**** 2
test(0)栈帧消失后会继续执行test(1)接下去的代码,同理test(2)也一样
例子四: 阶乘
def test(n):
if n==1:
return 1
else:
return n*test(n-1)
print(test(5))
#结果为:
120
分析同例子三一样!