函数_变量、参数、参数传递、浅拷贝和深拷贝、lambda、eval函数、递归函数

1501-姬同学

发表文章数:20

热门标签

首页 » Python » 正文

函数

函数也是对象,内存底层分析

在堆内存中创建好一个函数对象,可以反复多次调用

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

变量起作用的范围叫做变量的作用域,不同作用域内同名变量之间互不影响。
变量分为:全局变量、局部变量
全局变量:
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
标签:

未经允许不得转载:作者:1501-姬同学, 转载或复制请以 超链接形式 并注明出处 拜师资源博客
原文地址:《函数_变量、参数、参数传递、浅拷贝和深拷贝、lambda、eval函数、递归函数》 发布于2021-02-07

分享到:
赞(0) 打赏

评论 抢沙发

评论前必须登录!

  注册



长按图片转发给朋友

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏

Vieu3.3主题
专业打造轻量级个人企业风格博客主题!专注于前端开发,全站响应式布局自适应模板。

登录

忘记密码 ?

您也可以使用第三方帐号快捷登录

Q Q 登 录
微 博 登 录