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

分析同例子三一样!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值