函数也是对象_内存分析

Python中,“一切都是对象”。实际上,执行def定义函数后,系统就创建了相应的函数对象。
在定义时就已经创建好了函数对象,调用时无需创建,只是反复调用
执行如下程序,然后进行解释:

#定义时创建了test01变量存在栈中,test01的id指向堆中的值对象
def test01():print('daxian')
#函数带括号表示调用了该函数,执行了函数里面的代码
test01()
c = test01 #将test01中的值拷贝给c;c的id也指向了堆中值对象
c()#函数是对象,可以作为参数传递,也可以作为返回值返回print(id(test01))#test01和c的id相同
print(id(c))
print(type(c))#c也成为了funtion

71.变量的作用域(全局变量_局部变量)_栈帧内存分析

变量起作用的范围称为变量的作用域,不同作用城内同名变量之间互不影晌,变量分为:全局变量、局部变量。

全局变量:

1.在函数和定义之外声明的变量。作用域为定义的模块,从定义位置开始直到模块结束

2.全局变量降低了函数的通用性和可读性。尽量免全局变量的使用

3.全局量一般做常量使用。

4.函数内要改变全局变量的值,使global声明一下

局部变量:

1.在函数体中(包含形式数参数)声明的量

2.局部变量的引用比全局变量快,优先考虑使用

3.如果局变量和全局变量同名,则在函数内隐藏全局变量,只使用同可名的局部变量

【操作】全局变量的作用域测试

#测试全局变量和局部变量
a = 3 #全局变量
def test01():b = 4  #局部变量'''global a  想要使用全局a时需要global声明'''print(b*10)a = 300 #局部变量print(a)print(locals())#打印输出局部变量print(globals())#打印输出全局变量
test01() #当test01被调用会创建一个栈帧,栈帧里可以访问外面,外面不能访问栈帧里,当调用结束后栈帧销毁,就像胶卷
#print(b) 局部变量被使用会出错
print(a) #这个a是全局变量,和test()里的不是同一个a

72.局部变量和全局变量_效率测试

局部变量的查询和访问速度比全局变量快,优先考虑使用,尤其是在循环的时候。
在特别强调效率的地方或者循环次数较多的地方,可以通过将全局变量转为局部变量提高运行速度

#测试局部变量,全局变量的效率
import math
import time
def test01():start01 = time.time()for i in range(10000000):math.sqrt(30)end01 = time.time()print('耗时{}:'.format(end01-start01))
test01()def test02():b = math.sqrt ’‘’把函数对象赋给b,b就成为一个函数了math相当于一筐水果,sqrt是一个苹果,b相当于一个纸片如果在循环中使用math.sqrt(),就要每次去框里取,b = math.sqrt相当于把纸片当成了math.sqrt,循环中每次去使用纸片就可以了‘’‘# print(type(b)) →'''class 'builtin_function_or_method''''start02 = time.time()for i in range(10000000):b(30)end02 = time.time()print('耗时{}:'.format(end02 - start02))
test02()

73.参数的传递_传递可变对象_内存分析

函数的参数传递本质上就是:从实参到形参的赋值操作。 **Python中“一切皆对象”,所有的赋值操作都是“引用的赋值”。**所以,Python中参数的传递都是“引用传递”,不是“值传递”,具体操作时分为两类:

1.对“可变对象”进行“写操作”,直接作用于原对象本身。传递了可变对象以后,原对象和被传递的对象是同一个对象(引用)

2.对“不可变对象”进行“写操作”,会产生一个新的“对象空间”,并用新的值填充这块空间。(起到他语言的“值传递”效果,但不是“值传递”)

可变对象有:

字典、列表、集合、自定义的对象等

不可变对象有:

数字、字符串、元组、 function等

【操作】参数传递:传递可变对象的引用

b = [10,20]
def f2(m):print(id(m))m.append(30)print(id(m))
f2(b)
print(id(b))

传递可变对象的引用
传递参数是可变对象(例如:列表、字典、自定义的其他可变对象等),实际传递的还是对象的引用。在函数体中不创建新的对象拷贝,而是可以直接修改所传递的对象。

74.参数的传递_传递不可变对象_内存分析

传递参数是不可变对象(例:int、foat、字符串、元组布尔值),实际传递的还是对象的引用。在”赋值操作”时,由于不可变对象无法修改,系统会新创建一个对象。
【操作】参数传递:传递不可变对象的引用

a = 100 #定义全局变量
def f1(n):print('n:',id(n)) #传递进来的是a对象的地址,n在栈帧内也指向堆内100这个对象n = n + 200 #由于a是不可变对象,因此创建新的对象n;栈帧的引用消亡,重新指向300对象print('n',id(n)) #n已经变成了新的对象print(n)
f1(a) #a传递给n
print('a',id(a))

输出结果:

n: 4419577744
n 4422324464
300
a 4419577744

显然,通过id值我们可以看到n和a一开始是同一个对象。给n赋值后,n是新的对象。

75.浅拷贝和深拷贝_内存分析

为了更深入的了解参数传递的底层原理,我们需要讲解一下“浅拷贝和深拷贝”。我们可以使用内置函数:copy(浅拷贝)、 deepcopy(深拷贝)

浅拷贝:不拷贝子对象的内容,只是拷贝子对象的引用。(只拷贝自己,不拷贝儿子和孙子)

深拷贝:会连子对象的内存也全部拷贝一份,对子对象的修改不会影响源对象。(自己,儿子,孙子都拷贝)

浅拷贝实例:

import copy
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)

输出结果:

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
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)

输出结果:

a: [10, 20, [5, 6]]
b [10, 20, [5, 6]]
深拷贝
a: [10, 20, [5, 6]]
b: [10, 20, [5, 6, 7], 30]

76.参数的传递_不可变对象含可变子对象_内存分析

传递不可变对象用的是浅拷贝

传递参数是不可变对象(例如:int、float、字符串、元祖、布尔值),实际传递的还是对象的引用,但是在’写操作‘时,会创建一个新的对象拷贝。这个拷贝使用的是浅拷贝,不是深拷贝。

例1:

a = 10
print('a:',id(a))def test01(m):print('m:',id(m))#浅拷贝,m和a引用都指向10m = 20#m的引用指向20,原来指向print(m)print('m:', id(m))test01(a)#a传递进函数内

输出结果:

a: 4531346512
m: 4531346512
20
m: 4531346832

例2:

#传递不可变对象时,若不可变对象里面包含的子对象是可变的,则方法内修改了这个可变子对象,源对象也发生了变化
a = (10,20,[5,6])
print('a:',id(a))def test01(m):print('m:',id(m))#浅拷贝,m和a引用都指向10#m[0] =1 元组内的元素不能修改,会报错m[2][0] = 888print(m)print('m:', id(m))test01(a)#a传递进函数内
print(a)

输出结果:

a: 4396437088
m: 4396437088
(10, 20, [888, 6])
m: 4396437088
(10, 20, [888, 6])

77.参数的类型_位置参数_默认值参数_命名参数

位置参数

函数调用时,实参默认按位置顺序传递,需要个数和形参匹配。按位置传递的参数,称为:“位置参数”

def f1(a,b,c):print(a,b,c)f1(2,3,4)
f1(2,3)#报错,位置参数不匹配 TypeError: f1() missing 1 required positional argument: 'c'

默认值参数

我们可以为某些参数设置默认值,这样这些参数在传递时就是可选的。称为“默认值参数”。默认值参数放到位置数后面。

def f1(a,b,c=10,d=20): #默认值参数必须位于其他参数后面print(a,b,c,d)f1(2,3)
f1(2,3,4)
'''
2 3 10 20
2 3 4 20
'''

命名参数

我们也可以按照形参的名称传递参数,称为“命名参数”,也称“关键字参数”

def f1(a,b,c):print(a,b,c)f1(2,3,4)
f1(c=10,b=15,a=1)
'''
2 3 4
1 15 10
'''

78.参数的类型_可变参数_强制命名参数

可变参数

可变参数指的是“可变数量的参数”。分两种情况:

1.* param(一个星号),将多个参数收集到一个“元组”对象中

  1. **param(两个星号),将多个参数收集到一个“字典”对象中
def f(a,b,*c):print(a,b,c)def f2(d,e,**f):print(d,e,f)def f3(a,b,*c,**d):print(a,b,c,d)f(1,2,3,4,5)
f2(6,7,name = 'daxian',age = 18,job = 'teacher')
f3(1,2,3,4,5,name = 'daxian',age = 18,job = 'teacher')

强制命名参数

在带星号的“可变参数”后面增加新的参数,必须是“强制命名参数”

def f(*a,b,c):print(a,b,c)#f(1,2,3)  会报错,由于a是可变参数,将2,3,4全部收集,造成b,c没有赋值
f(1,b = 2,c = 3)

输出结果:

(1,) 2 3

79.lambda表达式和匿名函数

lambda表达式可以用来声明匿名函数。 lambda函数是一种简单的、在同一行中定义函数的方法, lambda函数实际生了一个函数对象。

lambda表达式只允许包含一个表达式,不能包含复杂语句,该表达式的计算结果就是函数的返回

lambda表达式基本语法:

lambda arg1,arg2,arg3… : <表达式>

arg1,arg2,arg3表示函数参数,<表达式>表示函数体。运行结果是表达式的计算结果。

【操作】lambda 表达式使用

f = lambda a,b,c : a + b + c
print(f)
print(f(1,2,3,))g = [lambda a:a*3, lambda b:b*4, lambda c:c*5]
print(g)
print(g[0](1),g[1](2),g[2](3))h = [f,f,f]
print(h[0](2,3,4))

输出结果:

<function <lambda> at 0x100758830>
6
[<function <lambda> at 0x100758a70>, <function <lambda> at 0x100758b00>, <function <lambda> at 0x100758b90>]
3 8 15
9

80.eval()函数用法

功能:将字符串str当成有效的表达式来求值并返回算结果。

语法:eval(source,globals[,locals])-> value

参数
source:一个Python表达式或函数compile()返回的代码对象

globals:可选,必须是dictionary

locals:可选。任意映射对象

s= 'print("abcd")'
eval(s)a = 10
b = 20
c = eval('(a+b)')
print(c)dict1 = dict(a=20,b=20)
d = eval('a+b',dict1)#在dict1中的a和b
print(d)

81.递归函数_函数调用内存分析_栈帧的创建

递归函数指的是:自己调用自己的函数,在函数体内部直接或间接的自己调用自己。递归类似于大家中学数学学习过的“数学归纳法”。

每个递归函数必须包含两个部分:
1.终止条件
表示递归什么时候结束。一般用于返回值,不再调用自己。
2.递归步骤
把第n步的值和第n-1步相关联

递归函数由于会创建大量的函数对象、过量的消耗内存和运算能力。在处理大量数时,谨慎使用。

内存调用分析

def test01():print('test01')test02()#test01()会报错def test02():print('test02')test01()

输出结果:

test01
test02

如上图,执行test01()时候,先启动一个test01栈帧,再启动一个test02栈帧,test02栈帧执行完后先消亡,test01()执行结束后再消亡。


如果在test01()中加入test01():

如上图:第一次创建一个test01栈帧,test01()内代码执行到第二行,又创建第二个test01栈帧,第二个test01()代码执行到第二行,又创建一个test01栈帧,如此循环反复,直到栈内存满了就会报错,因此要设置一个停止的条件。

---------------------------------------

如上图,当在第四次设置了停止条件,第三个栈帧就会执行test01()中之后的代码,代码执行结束,第三个test01栈帧就会消亡,执行第二个test01()剩余代码,执行结束后第二个test01栈帧消亡,知道所有栈帧消亡。


def test01(n):print(n)if n == 0:print('over')else:test01(n-1)
test01(4)


类似过滤器

def test01(n):print('test01:',n)if n == 0:print('over')else:test01(n-1)print('test01***',n)test01(4)

如上图,创建第一个栈帧,代码执行到test01(3),print(‘test01***’,4)未执行;创建第二个栈帧,代码执行到test01(2),print(‘test01***’,3)未执行…。直到over后,执行print(‘test01***’,0),然后第5个栈帧消亡,执行print(‘test01***’,1),然后第4个栈帧消亡…直到所有栈帧消亡。因此会输入如下结果:
test01: 4
test01: 3
test01: 2
test01: 1
test01: 0
over
test01*** 0
test01*** 1
test01*** 2
test01*** 3
test01*** 4

82.递归函数_阶乘计算案例

计算10以内的阶乘:

#使用递归函数计算阶乘
def factoria(n):if n == 1 :return 1else:return n*factoria(n-1) #套娃m = int(input('请输入1-10的整数:'))
for i in range(1,m+1):print('{0}!={1}'.format(i,factoria(i)))

以m=5为例子,分析内存:

求斐波那契数列第6项

def fibs(n):if n == 1 or n == 2:return 1else:return fibs(n-1)+fibs(n-2)
print(fibs(6))

83.嵌套函数_内部函数_数据隐藏

嵌套函数 :
在函数内部定义的函数。
【操作】嵌套函数定义、语法结构(

def f1():print('f1() running')def f2():print('f2 runnig')f2()
f1()

定义在f1()内,调用也在f1()内,f2()只为f1()服务。

一般在什么情况下使用嵌套函数?
1.封装-数据隐藏
外部无法访问“嵌套函数

2.贯彻 DRY(Don’t Repeat Yourself)原则
嵌套函数,可以让我们在函数内部避免重复代码

3.闭包
后面会详细讲解。

【操作】使用嵌套函数避免重复代码

def printChineseName(familyName,name):print('{0} {1}'.format(familyName,name))
def printEnglishName(name,familyName):print('{0} {1}'.format(name,familyName))'''
将上面的函数封装到嵌套函数中
'''
def printName(isChinese,familyName,name):def inner_print(a,b):print('{0} {1}'.format(a,b))if isChinese: #如果是中文名,把姓传给inner()中的a,名传给inner()中的binner_print(familyName,name)else:#如果是英文名,把姓传给inner()中的b,名传给inner()中的ainner_print(name,familyName)printName(True,'ding','daxian')
printName(False,'daxian','ding')

84.nonlocal_global

nonlocal:
用来声明外层的局部变量。

global
用来声明全局变量。

【操作】使用 nonlocal声明外层局变量

a =100def outer():b = 10def inner():nonlocal b #,声明外部函数的局部变量,不声明nonlocal就不能在inner内修改b,会报错print('inner:', b)b = 20global aa = 1000inner()print('inner:', b)print('inner:', a)outer()

85.LEGB规则

Python在查找“名称”时,是按照LEGB规则查找的:
Local–>Enclosed–>Global–>Built in(从内到外)

local指的就是函数或者类的方法内
Enclosed指的是嵌套函数(一个函数包裹另一个函数,闭包)
Global指的是横块中的全局变量
Built in指的是 Python为自己保留的特殊名称。

如果某个name映射在局部(local)命名空间中没有找到,接下来就会在闭包作用域( enclosed)进行搜索,如果闭包作用域也没有找到, Python就会到全局( global)命名空间中进行查找,最后会在内建(built-in)命名空间搜索(如果一个名称在所有命名空间中都没有找到,就会产生一个 (NameError)。

#测试LEGB
str('str(30)')
# str='global str'
def outer():#str = 'outer'def inner():#str='inner'print(str)inner()outer()

python夯实基础日记-函数详解相关推荐

  1. python夯实基础日记-类详解

    97.方法没有重载_方法的动态性 方法没有重载 在其他语言中,可以定义多个重名的方法,只要保证方法签名唯一即可.方法签名包含3个部分:方法名.参数数量.参数类型. Python中,方法的的参数没有类型 ...

  2. python average函数详解_python基础之函数详解

    Python基础之函数详解 一.函数的定义 到现在为止,我们已经掌握了Python的基本语法和数据类型等相关基础知识了,以进行一个项目的编写了,这个时候,就会发现,很多代码需要我们进行复制粘贴,这简直 ...

  3. Python seek()和tell()函数详解

    Python seek()和tell()函数详解 在讲解 seek() 函数和 tell() 函数之前,首先来了解一下什么是文件指针. 我们知道,使用 open() 函数打开文件并读取文件中的内容时, ...

  4. Python中的bbox_overlaps()函数详解

    Python中的bbox_overlaps()函数详解 想要编写自己的目标检测算法,就需要掌握bounding box(边界框)之间的关系.在这之中,bbox_overlaps()函数是一个非常实用的 ...

  5. python基础知识~ 函数详解2

    python~函数详解2  1 生成器函数    定义 如果函数有yield这个关键字,就是生成器函数.生成器函数() 获取的是生成器,不执行函数   须知 yield和return一样,都可以返回数 ...

  6. python夯实基础日记-for循环、优化技巧、函数

    057.for循环结构_遍历各种可迭代对象_range对象 for循环 通常用于可迭代对象的遍历,for循环的语法格式如下: for 变量 in 可迭代对象: 循环体语句 Python中的可迭代对象: ...

  7. python夯实基础日记-字典、集合、分支、循环

    43.字典_特点_4种创建方式_普通_dict_zip_formkeys 字典简介和特点 字典是"键值对"的无序可变序列,字典中的毎个元素都是一个"键值对",包 ...

  8. 【python基础】python中常用字符串函数详解

    文章目录 1 字符串查询(index,find) 2. 字符串大小写转换操作(upper.lower.swapcase.capitalize和title) 3. 字符串对齐(center,just和z ...

  9. python读取数据的函数详解_你了解文件缓存机制吗?磁盘文件如何读写?Python中open函数详解...

    我们知道,在使用Python打开一个文件时,一般使用的是open()函数,但是你真正了解这个函数么?文件打开后如何进行缓存?对于大文件它是如何处理的?今天,小编带你来详细了解一下-- Python如何 ...

最新文章

  1. Kotlin 使用list.add 时候报错的处理方法
  2. Delphi开发人员的编程习惯
  3. 【Linux】一步一步学Linux——compress命令(了解)(71)
  4. Java API —— Map接口
  5. mysql 以 db 结尾_MySQL的高级部分
  6. windows下同时安装python2与python3
  7. 【Xamarin】MonoTouch - iOS 使用 UIImagePickerController 打开图片库和相机选择图片修改头像...
  8. 用c++实现简单单链表,双链表,二叉树类
  9. OpenGL基础14:摄像机
  10. Android屏幕共享权限,chrome屏幕共享权限
  11. 定义结构体的三种方法(C语言)
  12. ap计算机知识点总结,AP统计学考试知识点汇总
  13. PM、GAN、InfoGAN、对抗自编码模型对比
  14. python去除字符串中表情字符
  15. 乐视X625手机刷机(全三个版本)官方包附刷机教程OEM解锁
  16. 关于 邵雍 渔樵问对 千古奇闻的思考
  17. 局域网IP变成广域网的IP
  18. HCIP之路重点LSA
  19. 百度网盘提速法,不用插件,不用安装脚本
  20. android应用间相互调用

热门文章

  1. win10 明明可以上网但显示无Internet的问题(已解决)
  2. 2022年5月8日 解决手机连接电脑无法选择“传输文件”
  3. dashboard 1.8
  4. COUNT、COUNTA、MAX三个函数在excel计数上的应用对比和案例
  5. VSCode 的 Live Share 功能终于来了
  6. windows10安装更新很慢ndows,Windows10下载更新一直不动,进度为0怎么办?
  7. webug4.0总结篇
  8. WeBug3.0靶场环境搭建与搭建资源分享
  9. python爬取股票行情_python爬取历史所有股票价格
  10. linux下 18 个实用的终端命令行工具